Skip to content

Commit

Permalink
Advanced networking settings (PhotonVision#899)
Browse files Browse the repository at this point in the history
Exposes NetworkManager interface name and more robustly handles device/interface names internally.

---------

Co-authored-by: Sriman Achanta <[email protected]>
  • Loading branch information
mcm001 and srimanachanta authored Sep 1, 2023
1 parent 08892b9 commit 306677e
Show file tree
Hide file tree
Showing 14 changed files with 315 additions and 92 deletions.
2 changes: 1 addition & 1 deletion photon-client/src/components/app/photon-sidebar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ const mdAndUp = computed<boolean>(() => getCurrentInstance()?.proxy.$vuetify.bre
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title class="text-wrap">
{{ useStateStore().backendConnected ? "Backend Connected" : "Trying to connect to Backend" }}
{{ useStateStore().backendConnected ? "Backend connected" : "Trying to connect to backend" }}
</v-list-item-title>
</v-list-item-content>
</v-list-item>
Expand Down
5 changes: 5 additions & 0 deletions photon-client/src/components/common/cv-select.vue
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ const localValue = computed({
// Computed in case items changes
const items = computed<SelectItem[]>(() => {
// Trivial case for empty list; we have no data
if (!props.items.length) {
return [];
}
// Check if the prop exists on the object to infer object type
if ((props.items[0] as SelectItem).name) {
return props.items as SelectItem[];
Expand Down
54 changes: 46 additions & 8 deletions photon-client/src/components/settings/NetworkingCard.vue
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
<script setup lang="ts">
import { useSettingsStore } from "@/stores/settings/GeneralSettingsStore";
import { ref } from "vue";
import { computed, ref } from "vue";
import CvInput from "@/components/common/cv-input.vue";
import CvRadio from "@/components/common/cv-radio.vue";
import CvSwitch from "@/components/common/cv-switch.vue";
import CvSelect from "@/components/common/cv-select.vue";
import { NetworkConnectionType } from "@/types/SettingTypes";
import { useStateStore } from "@/stores/StateStore";
Expand Down Expand Up @@ -76,6 +77,11 @@ const saveGeneralSettings = () => {
}
});
};
const currentNetworkInterfaceIndex = computed<number>({
get: () => useSettingsStore().networkInterfaceNames.indexOf(useSettingsStore().network.networkManagerIface || ""),
set: (v) => (useSettingsStore().network.networkManagerIface = useSettingsStore().networkInterfaceNames[v])
});
</script>

<template>
Expand All @@ -87,7 +93,7 @@ const saveGeneralSettings = () => {
v-model="useSettingsStore().network.ntServerAddress"
label="Team Number/NetworkTables Server Address"
tooltip="Enter the Team Number or the IP address of the NetworkTables Server"
:label-cols="3"
:label-cols="4"
:disabled="useSettingsStore().network.runNTServer"
:rules="[
(v) =>
Expand All @@ -109,33 +115,65 @@ const saveGeneralSettings = () => {
The NetworkTables Server Address is not set or is invalid. NetworkTables is unable to connect.
</v-banner>
<cv-radio
v-show="useSettingsStore().network.shouldManage"
v-model="useSettingsStore().network.connectionType"
label="IP Assignment Mode"
tooltip="DHCP will make the radio (router) automatically assign an IP address; this may result in an IP address that changes across reboots. Static IP assignment means that you pick the IP address and it won't change."
:input-cols="12 - 3"
:input-cols="12 - 4"
:list="['DHCP', 'Static']"
:disabled="!(useSettingsStore().network.shouldManage && useSettingsStore().network.canManage)"
/>
<cv-input
v-if="useSettingsStore().network.connectionType === NetworkConnectionType.Static"
v-model="useSettingsStore().network.staticIp"
:input-cols="12 - 3"
:input-cols="12 - 4"
label="Static IP"
:rules="[(v) => isValidIPv4(v) || 'Invalid IPv4 address']"
:disabled="!(useSettingsStore().network.shouldManage && useSettingsStore().network.canManage)"
/>
<cv-input
v-show="useSettingsStore().network.shouldManage"
v-model="useSettingsStore().network.hostname"
label="Hostname"
:input-cols="12 - 3"
:input-cols="12 - 4"
:rules="[(v) => isValidHostname(v) || 'Invalid hostname']"
:disabled="!(useSettingsStore().network.shouldManage && useSettingsStore().network.canManage)"
/>
<v-divider class="pb-3" />
<span style="font-weight: 700">Advanced Networking</span>
<cv-switch
v-model="useSettingsStore().network.shouldManage"
:disabled="!useSettingsStore().network.canManage"
label="Manage Device Networking"
tooltip="If enabled, Photon will manage device hostname and network settings."
:label-cols="4"
class="pt-2"
/>
<cv-select
v-model="currentNetworkInterfaceIndex"
label="NetworkManager interface"
:disabled="!(useSettingsStore().network.shouldManage && useSettingsStore().network.canManage)"
:select-cols="12 - 4"
tooltip="Name of the interface PhotonVision should manage the IP address of"
:items="useSettingsStore().networkInterfaceNames"
/>
<v-banner
v-show="
!useSettingsStore().networkInterfaceNames.length &&
useSettingsStore().network.shouldManage &&
useSettingsStore().network.canManage
"
rounded
color="red"
text-color="white"
icon="mdi-information-outline"
>
Photon cannot detect any wired connections! Please send program logs to the developers for help.
</v-banner>
<cv-switch
v-model="useSettingsStore().network.runNTServer"
label="Run NetworkTables Server (Debugging Only)"
tooltip="If enabled, this device will create a NT server. This is useful for home debugging, but should be disabled on-robot."
class="mt-3 mb-3"
:label-cols="3"
:label-cols="4"
/>
<v-banner
v-show="useSettingsStore().network.runNTServer"
Expand Down
23 changes: 19 additions & 4 deletions photon-client/src/stores/settings/GeneralSettingsStore.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { defineStore } from "pinia";
import type { GeneralSettings, LightingSettings, MetricData, NetworkSettings } from "@/types/SettingTypes";
import type {
GeneralSettings,
LightingSettings,
MetricData,
NetworkSettings,
ConfigurableNetworkSettings
} from "@/types/SettingTypes";
import { NetworkConnectionType } from "@/types/SettingTypes";
import { useStateStore } from "@/stores/StateStore";
import axios from "axios";
Expand All @@ -23,10 +29,17 @@ export const useSettingsStore = defineStore("settings", {
network: {
ntServerAddress: "",
shouldManage: true,
canManage: true,
connectionType: NetworkConnectionType.DHCP,
staticIp: "",
hostname: "photonvision",
runNTServer: false
runNTServer: false,
networkInterfaceNames: [
{
connName: "Example Wired Connection",
devName: "eth0"
}
]
},
lighting: {
supported: true,
Expand All @@ -47,6 +60,9 @@ export const useSettingsStore = defineStore("settings", {
getters: {
gpuAccelerationEnabled(): boolean {
return this.general.gpuAcceleration !== undefined;
},
networkInterfaceNames(): string[] {
return this.network.networkInterfaceNames.map((i) => i.connName);
}
},
actions: {
Expand Down Expand Up @@ -77,12 +93,11 @@ export const useSettingsStore = defineStore("settings", {
this.network = data.networkSettings;
},
saveGeneralSettings() {
const payload: Required<NetworkSettings> = {
const payload: Required<ConfigurableNetworkSettings> = {
connectionType: this.network.connectionType,
hostname: this.network.hostname,
networkManagerIface: this.network.networkManagerIface || "",
ntServerAddress: this.network.ntServerAddress,
physicalInterface: this.network.physicalInterface || "",
runNTServer: this.network.runNTServer,
setDHCPcommand: this.network.setDHCPcommand || "",
setStaticCommand: this.network.setStaticCommand || "",
Expand Down
10 changes: 9 additions & 1 deletion photon-client/src/types/SettingTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,27 @@ export enum NetworkConnectionType {
Static = 1
}

export interface NetworkInterfaceType {
connName: string;
devName: string;
}

export interface NetworkSettings {
ntServerAddress: string;
connectionType: NetworkConnectionType;
staticIp: string;
hostname: string;
runNTServer: boolean;
shouldManage: boolean;
canManage: boolean;
networkManagerIface?: string;
physicalInterface?: string;
setStaticCommand?: string;
setDHCPcommand?: string;
networkInterfaceNames: NetworkInterfaceType[];
}

export type ConfigurableNetworkSettings = Omit<NetworkSettings, "canManage" | "networkInterfaceNames">;

export interface LightingSettings {
supported: boolean;
brightness: number;
Expand Down
3 changes: 3 additions & 0 deletions photon-core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ dependencies {
implementation wpilibTools.deps.wpilibJava("apriltag")

implementation "org.xerial:sqlite-jdbc:3.41.0.0"

// NetworkManager
implementation "com.github.hypfvieh:dbus-java:3.3.2"
}

task writeCurrentVersionJava {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,12 @@ private void translateLegacyIfPresent(Path folderPath) {
// Cannot import into SQL if we aren't in SQL mode rn
return;
}
logger.info("Translating settings zip!");

var maybeCams = Path.of(folderPath.toAbsolutePath().toString(), "cameras").toFile();
var maybeCamsBak = Path.of(folderPath.toAbsolutePath().toString(), "cameras_backup").toFile();

if (maybeCams.exists() && maybeCams.isDirectory()) {
logger.info("Translating settings zip!");
var legacy = new LegacyConfigProvider(folderPath);
legacy.load();
var loadedConfig = legacy.getConfig();
Expand Down Expand Up @@ -264,6 +264,12 @@ public void unloadCameraConfigs() {
this.getConfig().getCameraConfigurations().clear();
}

public void clearConfig() {
logger.info("Clearing configuration!");
m_provider.clearConfig();
m_provider.saveToDisk();
}

public void saveToDisk() {
m_provider.saveToDisk();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import java.nio.file.Path;

public abstract class ConfigProvider {
private PhotonConfiguration config;
protected PhotonConfiguration config;

abstract void load();

Expand All @@ -30,6 +30,10 @@ PhotonConfiguration getConfig() {
return config;
}

public void clearConfig() {
config = new PhotonConfiguration();
}

public abstract boolean saveUploadedHardwareConfig(Path uploadPath);

public abstract boolean saveUploadedHardwareSettings(Path uploadPath);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,14 @@

import com.fasterxml.jackson.annotation.JsonAlias;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonGetter;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonSetter;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.HashMap;
import java.util.Map;
import org.photonvision.common.hardware.Platform;
import org.photonvision.common.networking.NetworkMode;
import org.photonvision.common.networking.NetworkUtils;
import org.photonvision.common.util.file.JacksonUtils;

public class NetworkConfig {
Expand All @@ -37,21 +36,31 @@ public class NetworkConfig {
public String staticIp = "";
public String hostname = "photonvision";
public boolean runNTServer = false;
public boolean shouldManage;

@JsonIgnore public static final String NM_IFACE_STRING = "${interface}";
@JsonIgnore public static final String NM_IP_STRING = "${ipaddr}";

public String networkManagerIface = "Wired connection 1";
public String physicalInterface = "eth0";
public String networkManagerIface;
public String setStaticCommand =
"nmcli con mod ${interface} ipv4.addresses ${ipaddr}/8 ipv4.method \"manual\" ipv6.method \"disabled\"";
public String setDHCPcommand =
"nmcli con mod ${interface} ipv4.method \"auto\" ipv6.method \"disabled\"";

private boolean shouldManage;

public NetworkConfig() {
setShouldManage(false);
if (Platform.isLinux()) {
// Default to the name of the first Ethernet connection. Otherwise, "Wired connection 1" is a
// reasonable guess
this.networkManagerIface =
NetworkUtils.getAllWiredInterfaces().stream()
.map(it -> it.connName)
.findFirst()
.orElse("Wired connection 1");
}

// We can (usually) manage networking on Linux devices, and if we can we should try to. Command
// line inhibitions happen at a level above this class
setShouldManage(deviceCanManageNetwork());
}

@JsonCreator
Expand All @@ -64,7 +73,6 @@ public NetworkConfig(
@JsonProperty("runNTServer") boolean runNTServer,
@JsonProperty("shouldManage") boolean shouldManage,
@JsonProperty("networkManagerIface") String networkManagerIface,
@JsonProperty("physicalInterface") String physicalInterface,
@JsonProperty("setStaticCommand") String setStaticCommand,
@JsonProperty("setDHCPcommand") String setDHCPcommand) {
this.ntServerAddress = ntServerAddress;
Expand All @@ -73,34 +81,45 @@ public NetworkConfig(
this.hostname = hostname;
this.runNTServer = runNTServer;
this.networkManagerIface = networkManagerIface;
this.physicalInterface = physicalInterface;
this.setStaticCommand = setStaticCommand;
this.setDHCPcommand = setDHCPcommand;
setShouldManage(shouldManage);
}

public Map<String, Object> toHashMap() {
try {
return new ObjectMapper().convertValue(this, JacksonUtils.UIMap.class);
var ret = new ObjectMapper().convertValue(this, JacksonUtils.UIMap.class);
ret.put("canManage", this.deviceCanManageNetwork());
return ret;
} catch (Exception e) {
e.printStackTrace();
return new HashMap<>();
}
}

@JsonIgnore
public String getEscapedIfaceName() {
public String getPhysicalInterfaceName() {
return NetworkUtils.getNMinfoForConnName(this.networkManagerIface).devName;
}

@JsonIgnore
public String getEscapedInterfaceName() {
return "\"" + networkManagerIface + "\"";
}

@JsonGetter("shouldManage")
@JsonIgnore
public boolean shouldManage() {
return this.shouldManage || Platform.isLinux();
return this.shouldManage;
}

@JsonSetter("shouldManage")
@JsonIgnore
public void setShouldManage(boolean shouldManage) {
this.shouldManage = shouldManage || Platform.isLinux();
this.shouldManage = shouldManage && this.deviceCanManageNetwork();
}

@JsonIgnore
private boolean deviceCanManageNetwork() {
return Platform.isLinux();
}

@Override
Expand All @@ -117,8 +136,6 @@ public String toString() {
+ runNTServer
+ ", networkManagerIface="
+ networkManagerIface
+ ", physicalInterface="
+ physicalInterface
+ ", setStaticCommand="
+ setStaticCommand
+ ", setDHCPcommand="
Expand Down
Loading

0 comments on commit 306677e

Please sign in to comment.