mirror of
https://github.com/PhotonVision/photonvision
synced 2026-06-24 01:31:44 +00:00
Advanced networking settings (#899)
Exposes NetworkManager interface name and more robustly handles device/interface names internally. --------- Co-authored-by: Sriman Achanta <68172138+srimanachanta@users.noreply.github.com>
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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[];
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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>
|
||||
@@ -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) =>
|
||||
@@ -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"
|
||||
|
||||
@@ -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";
|
||||
@@ -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,
|
||||
@@ -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: {
|
||||
@@ -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 || "",
|
||||
|
||||
@@ -24,6 +24,11 @@ export enum NetworkConnectionType {
|
||||
Static = 1
|
||||
}
|
||||
|
||||
export interface NetworkInterfaceType {
|
||||
connName: string;
|
||||
devName: string;
|
||||
}
|
||||
|
||||
export interface NetworkSettings {
|
||||
ntServerAddress: string;
|
||||
connectionType: NetworkConnectionType;
|
||||
@@ -31,12 +36,15 @@ export interface NetworkSettings {
|
||||
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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -83,12 +83,12 @@ public class ConfigManager {
|
||||
// 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();
|
||||
@@ -264,6 +264,12 @@ public class ConfigManager {
|
||||
this.getConfig().getCameraConfigurations().clear();
|
||||
}
|
||||
|
||||
public void clearConfig() {
|
||||
logger.info("Clearing configuration!");
|
||||
m_provider.clearConfig();
|
||||
m_provider.saveToDisk();
|
||||
}
|
||||
|
||||
public void saveToDisk() {
|
||||
m_provider.saveToDisk();
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ package org.photonvision.common.configuration;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public abstract class ConfigProvider {
|
||||
private PhotonConfiguration config;
|
||||
protected PhotonConfiguration config;
|
||||
|
||||
abstract void load();
|
||||
|
||||
@@ -30,6 +30,10 @@ public abstract class ConfigProvider {
|
||||
return config;
|
||||
}
|
||||
|
||||
public void clearConfig() {
|
||||
config = new PhotonConfiguration();
|
||||
}
|
||||
|
||||
public abstract boolean saveUploadedHardwareConfig(Path uploadPath);
|
||||
|
||||
public abstract boolean saveUploadedHardwareSettings(Path uploadPath);
|
||||
|
||||
@@ -19,15 +19,14 @@ package org.photonvision.common.configuration;
|
||||
|
||||
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 {
|
||||
@@ -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
|
||||
@@ -64,7 +73,6 @@ public class 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;
|
||||
@@ -73,7 +81,6 @@ public class NetworkConfig {
|
||||
this.hostname = hostname;
|
||||
this.runNTServer = runNTServer;
|
||||
this.networkManagerIface = networkManagerIface;
|
||||
this.physicalInterface = physicalInterface;
|
||||
this.setStaticCommand = setStaticCommand;
|
||||
this.setDHCPcommand = setDHCPcommand;
|
||||
setShouldManage(shouldManage);
|
||||
@@ -81,7 +88,9 @@ public class NetworkConfig {
|
||||
|
||||
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<>();
|
||||
@@ -89,18 +98,28 @@ public class NetworkConfig {
|
||||
}
|
||||
|
||||
@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
|
||||
@@ -117,8 +136,6 @@ public class NetworkConfig {
|
||||
+ runNTServer
|
||||
+ ", networkManagerIface="
|
||||
+ networkManagerIface
|
||||
+ ", physicalInterface="
|
||||
+ physicalInterface
|
||||
+ ", setStaticCommand="
|
||||
+ setStaticCommand
|
||||
+ ", setDHCPcommand="
|
||||
|
||||
@@ -24,6 +24,7 @@ import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import org.photonvision.PhotonVersion;
|
||||
import org.photonvision.common.hardware.Platform;
|
||||
import org.photonvision.common.networking.NetworkUtils;
|
||||
import org.photonvision.common.util.SerializationUtils;
|
||||
import org.photonvision.raspi.LibCameraJNI;
|
||||
import org.photonvision.vision.processes.VisionModule;
|
||||
@@ -55,6 +56,10 @@ public class PhotonConfiguration {
|
||||
this.cameraConfigurations = cameraConfigurations;
|
||||
}
|
||||
|
||||
public PhotonConfiguration() {
|
||||
this(new HardwareConfig(), new HardwareSettings(), new NetworkConfig());
|
||||
}
|
||||
|
||||
public HardwareConfig getHardwareConfig() {
|
||||
return hardwareConfig;
|
||||
}
|
||||
@@ -93,7 +98,12 @@ public class PhotonConfiguration {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
var settingsSubmap = new HashMap<String, Object>();
|
||||
|
||||
settingsSubmap.put("networkSettings", networkConfig.toHashMap());
|
||||
// Hack active interfaces into networkSettings
|
||||
var netConfigMap = networkConfig.toHashMap();
|
||||
netConfigMap.put("networkInterfaceNames", NetworkUtils.getAllWiredInterfaces());
|
||||
|
||||
settingsSubmap.put("networkSettings", netConfigMap);
|
||||
|
||||
map.put(
|
||||
"cameraSettings",
|
||||
VisionModuleManager.getInstance().getModules().stream()
|
||||
@@ -140,17 +150,4 @@ public class PhotonConfiguration {
|
||||
public List<HashMap<String, Object>> calibrations;
|
||||
public boolean isFovConfigurable = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PhotonConfiguration [hardwareConfig="
|
||||
+ hardwareConfig
|
||||
+ ", hardwareSettings="
|
||||
+ hardwareSettings
|
||||
+ ", networkConfig="
|
||||
+ networkConfig
|
||||
+ ", cameraConfigurations="
|
||||
+ cameraConfigurations
|
||||
+ "]";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,7 +62,6 @@ public class SqlConfigProvider extends ConfigProvider {
|
||||
private static final String dbName = "photon.sqlite";
|
||||
private final String dbPath;
|
||||
|
||||
private PhotonConfiguration config;
|
||||
private final Object m_mutex = new Object();
|
||||
private final File rootFolder;
|
||||
|
||||
|
||||
@@ -38,9 +38,10 @@ public class NetworkManager {
|
||||
}
|
||||
|
||||
private boolean isManaged = false;
|
||||
public boolean networkingIsDisabled = false; // Passed in via CLI
|
||||
|
||||
public void initialize(boolean shouldManage) {
|
||||
isManaged = shouldManage;
|
||||
isManaged = shouldManage && !networkingIsDisabled;
|
||||
if (!isManaged) {
|
||||
return;
|
||||
}
|
||||
@@ -99,8 +100,8 @@ public class NetworkManager {
|
||||
// set nmcli back to DHCP, and re-run dhclient -- this ought to grab a new IP address
|
||||
shell.executeBashCommand(
|
||||
config.setDHCPcommand.replace(
|
||||
NetworkConfig.NM_IFACE_STRING, config.getEscapedIfaceName()));
|
||||
shell.executeBashCommand("dhclient " + config.physicalInterface, false);
|
||||
NetworkConfig.NM_IFACE_STRING, config.getEscapedInterfaceName()));
|
||||
shell.executeBashCommand("dhclient " + config.getPhysicalInterfaceName(), false);
|
||||
} catch (Exception e) {
|
||||
logger.error("Exception while setting DHCP!");
|
||||
}
|
||||
@@ -111,7 +112,7 @@ public class NetworkManager {
|
||||
shell.executeBashCommand(
|
||||
config
|
||||
.setStaticCommand
|
||||
.replace(NetworkConfig.NM_IFACE_STRING, config.getEscapedIfaceName())
|
||||
.replace(NetworkConfig.NM_IFACE_STRING, config.getEscapedInterfaceName())
|
||||
.replace(NetworkConfig.NM_IP_STRING, config.staticIp));
|
||||
|
||||
if (Platform.isRaspberryPi()) {
|
||||
@@ -119,17 +120,17 @@ public class NetworkManager {
|
||||
// integral in my testing (Matt)
|
||||
shell.executeBashCommand(
|
||||
"sh -c 'nmcli con down "
|
||||
+ config.getEscapedIfaceName()
|
||||
+ config.getEscapedInterfaceName()
|
||||
+ "; nmcli con up "
|
||||
+ config.getEscapedIfaceName()
|
||||
+ config.getEscapedInterfaceName()
|
||||
+ "'");
|
||||
} else {
|
||||
// for now just bring down /up -- more testing needed on beelink et al
|
||||
shell.executeBashCommand(
|
||||
"sh -c 'nmcli con down "
|
||||
+ config.getEscapedIfaceName()
|
||||
+ config.getEscapedInterfaceName()
|
||||
+ "; nmcli con up "
|
||||
+ config.getEscapedIfaceName()
|
||||
+ config.getEscapedInterfaceName()
|
||||
+ "'");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* Copyright (C) Photon Vision.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.common.networking;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import org.photonvision.common.hardware.Platform;
|
||||
import org.photonvision.common.logging.LogGroup;
|
||||
import org.photonvision.common.logging.Logger;
|
||||
import org.photonvision.common.util.ShellExec;
|
||||
|
||||
public class NetworkUtils {
|
||||
private static final Logger logger = new Logger(NetworkUtils.class, LogGroup.General);
|
||||
|
||||
public static enum NMType {
|
||||
NMTYPE_ETHERNET("ethernet"),
|
||||
NMTYPE_WIFI("wifi"),
|
||||
NMTYPE_UNKNOWN("");
|
||||
|
||||
private NMType(String id) {
|
||||
identifier = id;
|
||||
}
|
||||
|
||||
private final String identifier;
|
||||
|
||||
public static NMType typeForString(String s) {
|
||||
for (var t : NMType.values()) {
|
||||
if (t.identifier.equals(s)) {
|
||||
return t;
|
||||
}
|
||||
}
|
||||
return NMTYPE_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
public static class NMDeviceInfo {
|
||||
public NMDeviceInfo(String c, String d, String type) {
|
||||
connName = c;
|
||||
devName = d;
|
||||
nmType = NMType.typeForString(type);
|
||||
}
|
||||
|
||||
public final String connName; // Human readable name used by "nmcli con"
|
||||
public final String devName; // underlying device, used by dhclient
|
||||
public final NMType nmType;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "NMDeviceInfo [connName="
|
||||
+ connName
|
||||
+ ", devName="
|
||||
+ devName
|
||||
+ ", nmType="
|
||||
+ nmType
|
||||
+ "]";
|
||||
}
|
||||
}
|
||||
|
||||
public static ArrayList<NMDeviceInfo> getAllInterfaces() {
|
||||
var ret = new ArrayList<NMDeviceInfo>();
|
||||
|
||||
if (!Platform.isLinux()) {
|
||||
// Can't determine interface name on Linux, give up
|
||||
return ret;
|
||||
}
|
||||
|
||||
try {
|
||||
var shell = new ShellExec(true, true);
|
||||
shell.executeBashCommand(
|
||||
"nmcli -t -f GENERAL.CONNECTION,GENERAL.DEVICE,GENERAL.TYPE device show");
|
||||
String out = shell.getOutput();
|
||||
if (out == null) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
Pattern pattern =
|
||||
Pattern.compile("GENERAL.CONNECTION:(.*)\nGENERAL.DEVICE:(.*)\nGENERAL.TYPE:(.*)");
|
||||
Matcher matcher = pattern.matcher(out);
|
||||
while (matcher.find()) {
|
||||
ret.add(new NMDeviceInfo(matcher.group(1), matcher.group(2), matcher.group(3)));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.error("Could not get active NM ifaces!", e);
|
||||
}
|
||||
|
||||
logger.debug("Found network interfaces:\n" + ret.toString());
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static List<NMDeviceInfo> getAllActiveInterfaces() {
|
||||
// Seems like if a interface exists but isn't actually connected, the connection name will be an
|
||||
// empty string. Check here and only return connections with non-empty names
|
||||
return getAllInterfaces().stream()
|
||||
.filter(it -> !it.connName.trim().isEmpty())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public static List<NMDeviceInfo> getAllWiredInterfaces() {
|
||||
return getAllActiveInterfaces().stream()
|
||||
.filter(it -> it.nmType == NMType.NMTYPE_ETHERNET)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public static NMDeviceInfo getNMinfoForConnName(String connName) {
|
||||
for (NMDeviceInfo info : getAllActiveInterfaces()) {
|
||||
if (info.connName.equals(connName)) {
|
||||
return info;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -80,6 +80,12 @@ public class Main {
|
||||
"ignore-cameras",
|
||||
true,
|
||||
"Ignore cameras that match a regex. Uses camera name as provided by cscore.");
|
||||
options.addOption("n", "disable-networking", false, "Disables control device network settings");
|
||||
options.addOption(
|
||||
"c",
|
||||
"clear-config",
|
||||
false,
|
||||
"Clears PhotonVision pipeline and networking settings. Preserves log files");
|
||||
|
||||
CommandLineParser parser = new DefaultParser();
|
||||
CommandLine cmd = parser.parse(options, args);
|
||||
@@ -109,6 +115,14 @@ public class Main {
|
||||
VisionSourceManager.getInstance()
|
||||
.setIgnoredCamerasRegex(cmd.getOptionValue("ignore-cameras"));
|
||||
}
|
||||
|
||||
if (cmd.hasOption("disable-networking")) {
|
||||
NetworkManager.getInstance().networkingIsDisabled = true;
|
||||
}
|
||||
|
||||
if (cmd.hasOption("clear-config")) {
|
||||
ConfigManager.getInstance().clearConfig();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -117,46 +131,29 @@ public class Main {
|
||||
ConfigManager.getInstance().load();
|
||||
|
||||
try {
|
||||
var reflective = new ReflectivePipelineSettings();
|
||||
var shape = new ColoredShapePipelineSettings();
|
||||
var aprilTag = new AprilTagPipelineSettings();
|
||||
List<VisionSource> collectedSources =
|
||||
Files.list(testModeFolder)
|
||||
.filter(p -> p.toFile().isFile())
|
||||
.map(
|
||||
p -> {
|
||||
try {
|
||||
// var camConf =
|
||||
//
|
||||
// ConfigManager.getInstance()
|
||||
// .getConfig()
|
||||
//
|
||||
// .getCameraConfigurations()
|
||||
//
|
||||
// .get(p.getFileName().toString());
|
||||
CameraConfiguration camConf =
|
||||
new CameraConfiguration(
|
||||
p.getFileName().toString(), p.toAbsolutePath().toString());
|
||||
camConf.FOV = TestUtils.WPI2019Image.FOV; // Good guess?
|
||||
camConf.addCalibration(TestUtils.get2020LifeCamCoeffs(false));
|
||||
|
||||
// if (camConf == null && false) {
|
||||
CameraConfiguration camConf;
|
||||
if (true) {
|
||||
camConf =
|
||||
new CameraConfiguration(
|
||||
p.getFileName().toString(), p.toAbsolutePath().toString());
|
||||
camConf.FOV = TestUtils.WPI2019Image.FOV; // Good guess?
|
||||
camConf.addCalibration(TestUtils.get2020LifeCamCoeffs(false));
|
||||
var pipeSettings = new AprilTagPipelineSettings();
|
||||
pipeSettings.pipelineNickname = p.getFileName().toString();
|
||||
pipeSettings.outputShowMultipleTargets = true;
|
||||
pipeSettings.inputShouldShow = true;
|
||||
pipeSettings.outputShouldShow = false;
|
||||
pipeSettings.solvePNPEnabled = true;
|
||||
|
||||
var pipeSettings = new AprilTagPipelineSettings();
|
||||
pipeSettings.pipelineNickname = p.getFileName().toString();
|
||||
pipeSettings.outputShowMultipleTargets = true;
|
||||
pipeSettings.inputShouldShow = true;
|
||||
pipeSettings.outputShouldShow = false;
|
||||
pipeSettings.solvePNPEnabled = true;
|
||||
|
||||
var psList = new ArrayList<CVPipelineSettings>();
|
||||
// psList.add(reflective);
|
||||
// psList.add(shape);
|
||||
psList.add(aprilTag);
|
||||
camConf.pipelineSettings = psList;
|
||||
}
|
||||
var aprilTag = new AprilTagPipelineSettings();
|
||||
var psList = new ArrayList<CVPipelineSettings>();
|
||||
psList.add(aprilTag);
|
||||
camConf.pipelineSettings = psList;
|
||||
|
||||
return new FileVisionSource(camConf);
|
||||
} catch (Exception e) {
|
||||
@@ -313,9 +310,11 @@ public class Main {
|
||||
}
|
||||
|
||||
try {
|
||||
LibCameraJNI.forceLoad();
|
||||
if (Platform.isRaspberryPi()) {
|
||||
LibCameraJNI.forceLoad();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.error("Failed to load native libraries!", e);
|
||||
logger.error("Failed to load libcamera-JNI!", e);
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user