Skip to content

Commit

Permalink
feat(nm): add GPS enable/disable support (#4476)
Browse files Browse the repository at this point in the history
* feat: add toBitmask converter method

* feat(nm): first implementation of GPS enable support

* refactor: rename method to "toBitMaskFromMMModemLocationSource" for consistency

* test(nm): add toBitMaskFromMMModemLocationSource method test

* refactor: fix names

* feat(nm): disable GPS when interface is disabled

* test(nm): parametrized for deviceId, interfaceName

* test: initial support for ModemLocation tests

* test(nm): added Modem configuration tests with GPS

* refactor: invert expectations

* test: improve redability of thenLocationSetupWasCalledWith method

* refactor: add missing `final`

* fix: merge issue

* test(nm): add test for missing GPS configuration

* refactor: lower log level

* refactor: group together configuration methods

* feat(nm): enable/disable GPS independently of Modem ip status

* feat(nm): preserve all other not managed location sources configuration

* refactor(nm): simplify GPS handling logic
  • Loading branch information
mattdibi authored Mar 24, 2023
1 parent ac020c3 commit 94c9145
Show file tree
Hide file tree
Showing 4 changed files with 359 additions and 111 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,13 @@ public static Set<MMModemLocationSource> toMMModemLocationSourceFromBitMask(UInt
}
return modemLocationSources;
}

public static UInt32 toBitMaskFromMMModemLocationSource(Set<MMModemLocationSource> desiredLocationSources) {
long bitmask = 0x00000000;
for (MMModemLocationSource source : desiredLocationSources) {
bitmask = (bitmask | source.value);
}

return new UInt32(bitmask);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;

import org.eclipse.kura.KuraException;
import org.eclipse.kura.executor.CommandExecutorService;
Expand All @@ -41,6 +43,7 @@
import org.freedesktop.dbus.types.UInt32;
import org.freedesktop.dbus.types.Variant;
import org.freedesktop.modemmanager1.Modem;
import org.freedesktop.modemmanager1.modem.Location;
import org.freedesktop.networkmanager.Device;
import org.freedesktop.networkmanager.Settings;
import org.freedesktop.networkmanager.device.Generic;
Expand All @@ -63,6 +66,7 @@ public class NMDbusConnector {
private static final String MM_BUS_NAME = "org.freedesktop.ModemManager1";
private static final String MM_BUS_PATH = "/org/freedesktop/ModemManager1";
private static final String MM_MODEM_NAME = "org.freedesktop.ModemManager1.Modem";
private static final String MM_LOCATION_BUS_NAME = "org.freedesktop.ModemManager1.Modem.Location";

private static final String NM_PROPERTY_VERSION = "Version";

Expand Down Expand Up @@ -301,37 +305,47 @@ private synchronized void manageConfiguredInterfaces(List<String> configuredInte
}
}

private synchronized void manageConfiguredInterface(String iface, NetworkProperties properties)
private synchronized void manageConfiguredInterface(String deviceId, NetworkProperties properties)
throws DBusException {
Optional<Device> device = getDeviceByInterfaceId(iface);
if (device.isPresent()) {
NMDeviceType deviceType = getDeviceType(device.get());
Optional<Device> device = getDeviceByInterfaceId(deviceId);
if (!device.isPresent()) {
logger.warn("Device \"{}\" cannot be found. Skipping configuration.", deviceId);
return;
}

KuraIpStatus ip4Status = KuraIpStatus
.fromString(properties.get(String.class, "net.interface.%s.config.ip4.status", iface));
// Temporary solution while we wait to add complete IPv6 support
KuraIpStatus ip6Status = ip4Status == KuraIpStatus.UNMANAGED ? KuraIpStatus.UNMANAGED
: KuraIpStatus.DISABLED;
KuraInterfaceStatus interfaceStatus = KuraInterfaceStatus.fromKuraIpStatus(ip4Status, ip6Status);

if (!CONFIGURATION_SUPPORTED_DEVICE_TYPES.contains(deviceType)
|| !CONFIGURATION_SUPPORTED_STATUSES.contains(ip4Status)
|| !CONFIGURATION_SUPPORTED_STATUSES.contains(ip6Status)) {
logger.warn("Device \"{}\" of type \"{}\" with status \"{}\"/\"{}\" currently not supported", iface,
deviceType, ip4Status, ip6Status);
return;
}
NMDeviceType deviceType = getDeviceType(device.get());

logger.info("Settings iface \"{}\":{}", iface, deviceType);
KuraIpStatus ip4Status = KuraIpStatus
.fromString(properties.get(String.class, "net.interface.%s.config.ip4.status", deviceId));
// Temporary solution while we wait to add complete IPv6 support
KuraIpStatus ip6Status = ip4Status == KuraIpStatus.UNMANAGED ? KuraIpStatus.UNMANAGED : KuraIpStatus.DISABLED;
KuraInterfaceStatus interfaceStatus = KuraInterfaceStatus.fromKuraIpStatus(ip4Status, ip6Status);

if (interfaceStatus == KuraInterfaceStatus.DISABLED) {
disable(device.get());
} else if (interfaceStatus == KuraInterfaceStatus.UNMANAGED) {
logger.info("Iface \"{}\" set as UNMANAGED in Kura. Skipping configuration.", iface);
} else { // NMDeviceEnable.ENABLED
enableInterface(iface, properties, device.get(), deviceType);
}
if (!CONFIGURATION_SUPPORTED_DEVICE_TYPES.contains(deviceType)
|| !CONFIGURATION_SUPPORTED_STATUSES.contains(ip4Status)
|| !CONFIGURATION_SUPPORTED_STATUSES.contains(ip6Status)) {
logger.warn("Device \"{}\" of type \"{}\" with status \"{}\"/\"{}\" currently not supported", deviceId,
deviceType, ip4Status, ip6Status);
return;
}

logger.info("Settings iface \"{}\":{}", deviceId, deviceType);

if (interfaceStatus == KuraInterfaceStatus.DISABLED) {
disable(device.get());
} else if (interfaceStatus == KuraInterfaceStatus.UNMANAGED) {
logger.info("Iface \"{}\" set as UNMANAGED in Kura. Skipping configuration.", deviceId);
} else { // NMDeviceEnable.ENABLED
enableInterface(deviceId, properties, device.get(), deviceType);
}

// Manage GPS independently of device ip status
if (deviceType == NMDeviceType.NM_DEVICE_TYPE_MODEM) {
Optional<Boolean> enableGPS = properties.getOpt(Boolean.class, "net.interface.%s.config.gpsEnabled",
deviceId);
handleModemManagerGPSSetup(device.get(), enableGPS);
}

}

private void enableInterface(String deviceId, NetworkProperties properties, Device device, NMDeviceType deviceType)
Expand Down Expand Up @@ -360,7 +374,6 @@ private void enableInterface(String deviceId, NetworkProperties properties, Devi
this.nm.AddAndActivateConnection(newConnectionSettings, new DBusPath(device.getObjectPath()),
new DBusPath("/"));
}

dsLock.waitForSignal();

}
Expand All @@ -382,11 +395,68 @@ private synchronized void manageNonConfiguredInterfaces(List<String> configuredI

if (!configuredInterfaces.contains(ipInterface)) {
logger.warn("Device \"{}\" of type \"{}\" not configured. Disabling...", ipInterface, deviceType);

disable(device);

if (deviceType == NMDeviceType.NM_DEVICE_TYPE_MODEM) {
handleModemManagerGPSSetup(device, Optional.of(false));
}
}
}
}

private void disable(Device device) throws DBusException {
NMDeviceState deviceState = getDeviceState(device);
if (Boolean.TRUE.equals(NMDeviceState.isConnected(deviceState))) {
DeviceStateLock dsLock = new DeviceStateLock(this.dbusConnection, device.getObjectPath(),
NMDeviceState.NM_DEVICE_STATE_DISCONNECTED);
device.Disconnect();
dsLock.waitForSignal();
}

Optional<Connection> connection = getAppliedConnection(device);
if (connection.isPresent()) {
connection.get().Delete();
}
}

private void handleModemManagerGPSSetup(Device device, Optional<Boolean> enableGPS) throws DBusException {
Optional<String> modemDevicePath = getModemPathFromMM(device.getObjectPath());

if (!modemDevicePath.isPresent()) {
logger.warn("Cannot retrieve MM.Modem from NM.Modem at path: {}. Skipping GPS configuration.",
device.getObjectPath());
return;
}

boolean isGPSSourceEnabled = enableGPS.isPresent() && enableGPS.get();

Location modemLocation = this.dbusConnection.getRemoteObject(MM_BUS_NAME, modemDevicePath.get(),
Location.class);
Properties modemLocationProperties = this.dbusConnection.getRemoteObject(MM_BUS_NAME,
modemLocation.getObjectPath(), Properties.class);

Set<MMModemLocationSource> availableLocationSources = MMModemLocationSource
.toMMModemLocationSourceFromBitMask(modemLocationProperties.Get(MM_LOCATION_BUS_NAME, "Capabilities"));
Set<MMModemLocationSource> currentLocationSources = MMModemLocationSource
.toMMModemLocationSourceFromBitMask(modemLocationProperties.Get(MM_LOCATION_BUS_NAME, "Enabled"));
EnumSet<MMModemLocationSource> managedLocationSources = EnumSet.of(
MMModemLocationSource.MM_MODEM_LOCATION_SOURCE_GPS_RAW,
MMModemLocationSource.MM_MODEM_LOCATION_SOURCE_GPS_NMEA);

for (MMModemLocationSource managedSource : managedLocationSources) {
if (isGPSSourceEnabled && availableLocationSources.contains(managedSource)) {
currentLocationSources.add(managedSource);
} else {
currentLocationSources.remove(managedSource);
}
}

logger.debug("Modem location setup {} for modem {}", currentLocationSources, modemDevicePath.get());

modemLocation.Setup(MMModemLocationSource.toBitMaskFromMMModemLocationSource(currentLocationSources), false);
}

private String getDeviceId(Device device) throws DBusException {
NMDeviceType deviceType = getDeviceType(device);
if (deviceType.equals(NMDeviceType.NM_DEVICE_TYPE_MODEM)) {
Expand All @@ -409,21 +479,6 @@ private String getDeviceId(Device device) throws DBusException {
}
}

private void disable(Device device) throws DBusException {
NMDeviceState deviceState = getDeviceState(device);
if (Boolean.TRUE.equals(NMDeviceState.isConnected(deviceState))) {
DeviceStateLock dsLock = new DeviceStateLock(this.dbusConnection, device.getObjectPath(),
NMDeviceState.NM_DEVICE_STATE_DISCONNECTED);
device.Disconnect();
dsLock.waitForSignal();
}

Optional<Connection> connection = getAppliedConnection(device);
if (connection.isPresent()) {
connection.get().Delete();
}
}

private List<Device> getAllDevices() throws DBusException {
List<DBusPath> devicePaths = this.nm.GetAllDevices();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public class MMModemLocationSourceTest {
public static class MMModemLocationSourceToMMModemLocationSourceTest {

@Parameters
public static Collection<Object[]> LocationSourceParams() {
public static Collection<Object[]> locationSourceParams() {
List<Object[]> params = new ArrayList<>();
params.add(new Object[] { new UInt32(0x00000000L), MMModemLocationSource.MM_MODEM_LOCATION_SOURCE_NONE });
params.add(new Object[] { new UInt32(0x00000001L),
Expand Down Expand Up @@ -83,7 +83,7 @@ private void thenCalculatedMMModemLocationSourceIsCorrect() {
public static class MMModemLocationSourceToMMModemLocationSourceFromBitMaskTest {

@Parameters
public static Collection<Object[]> LocationSourceParams() {
public static Collection<Object[]> locationSourceParams() {
List<Object[]> params = new ArrayList<>();
params.add(new Object[] { new UInt32(0x00000000L),
EnumSet.of(MMModemLocationSource.MM_MODEM_LOCATION_SOURCE_NONE) });
Expand Down Expand Up @@ -115,22 +115,81 @@ public MMModemLocationSourceToMMModemLocationSourceFromBitMaskTest(UInt32 intVal
}

@Test
public void shouldReturnCorrectIpFamily() {
whenCalculatedBearerIpType();
thenCalculatedBearerIpTypeIsCorrect();
public void shouldReturnCorrectLocationSourceSet() {
whenConversionMethodIsCalled();
thenCalculatedLocationSourceSetMatches();
}

private void whenCalculatedBearerIpType() {
private void whenConversionMethodIsCalled() {
this.calculatedLocationSource = MMModemLocationSource
.toMMModemLocationSourceFromBitMask(this.inputIntValue);
}

private void thenCalculatedBearerIpTypeIsCorrect() {
private void thenCalculatedLocationSourceSetMatches() {
assertEquals(this.expectedLocationSource, this.calculatedLocationSource);
}

}

@RunWith(Parameterized.class)
public static class MMModemLocationSourceToBitMaskFromMMModemLocationSourceTest {

@Parameters
public static Collection<Object[]> locationSourceParams() {
List<Object[]> params = new ArrayList<>();
params.add(new Object[] { EnumSet.of(MMModemLocationSource.MM_MODEM_LOCATION_SOURCE_NONE),
new UInt32(0x00000000L) });
params.add(
new Object[] {
EnumSet.of(MMModemLocationSource.MM_MODEM_LOCATION_SOURCE_3GPP_LAC_CI,
MMModemLocationSource.MM_MODEM_LOCATION_SOURCE_GPS_RAW),
new UInt32(0x00000003L), });
params.add(
new Object[] {
EnumSet.of(MMModemLocationSource.MM_MODEM_LOCATION_SOURCE_3GPP_LAC_CI,
MMModemLocationSource.MM_MODEM_LOCATION_SOURCE_GPS_RAW,
MMModemLocationSource.MM_MODEM_LOCATION_SOURCE_GPS_NMEA),
new UInt32(0x00000007L) });
params.add(
new Object[] {
EnumSet.of(MMModemLocationSource.MM_MODEM_LOCATION_SOURCE_GPS_RAW,
MMModemLocationSource.MM_MODEM_LOCATION_SOURCE_GPS_NMEA),
new UInt32(0x00000006L) });
params.add(new Object[] { EnumSet.of(MMModemLocationSource.MM_MODEM_LOCATION_SOURCE_GPS_NMEA),
new UInt32(0x00000004L) });
params.add(new Object[] { EnumSet.of(MMModemLocationSource.MM_MODEM_LOCATION_SOURCE_3GPP_LAC_CI,
MMModemLocationSource.MM_MODEM_LOCATION_SOURCE_GPS_RAW,
MMModemLocationSource.MM_MODEM_LOCATION_SOURCE_GPS_NMEA,
MMModemLocationSource.MM_MODEM_LOCATION_SOURCE_CDMA_BS), new UInt32(0x0000000FL) });
return params;
}

private final Set<MMModemLocationSource> inputSet;
private final UInt32 expectedBitmask;
private UInt32 calculatedBitmask;

public MMModemLocationSourceToBitMaskFromMMModemLocationSourceTest(Set<MMModemLocationSource> locationSources,
UInt32 bitmask) {
this.inputSet = locationSources;
this.expectedBitmask = bitmask;
}

@Test
public void shouldReturnCorrectBitmask() {
whenMethodIsCalled();
thenCalculatedAndExpectedBitmaskMatch();
}

private void whenMethodIsCalled() {
this.calculatedBitmask = MMModemLocationSource.toBitMaskFromMMModemLocationSource(this.inputSet);
}

private void thenCalculatedAndExpectedBitmaskMatch() {
assertEquals(this.expectedBitmask, this.calculatedBitmask);
}

}

@RunWith(Parameterized.class)
public static class MMModemLocationSourceToUInt32Test {

Expand Down
Loading

0 comments on commit 94c9145

Please sign in to comment.