calibrations,
- @JsonProperty("currentPipelineIndex") int currentPipelineIndex) {
+ String uniqueName,
+ PVCameraInfo matchedCameraInfo,
+ String nickname,
+ boolean deactivated,
+ QuirkyCamera cameraQuirks,
+ double FOV,
+ int currentPipelineIndex) {
this.uniqueName = uniqueName;
this.matchedCameraInfo = matchedCameraInfo;
this.nickname = nickname;
this.deactivated = deactivated;
this.cameraQuirks = cameraQuirks;
this.FOV = FOV;
- this.calibrations = calibrations != null ? calibrations : new ArrayList<>();
this.currentPipelineIndex = currentPipelineIndex;
}
@@ -120,14 +114,14 @@ public class CameraConfiguration {
PVCameraInfo matchedCameraInfo;
/** Legacy constructor for compat with 2024.3.1 */
- @JsonCreator
+ @Json.Creator
public LegacyCameraConfigStruct(
- @JsonProperty("baseName") String baseName,
- @JsonProperty("path") String path,
- @JsonProperty("otherPaths") String[] otherPaths,
- @JsonProperty("cameraType") CameraType cameraType,
- @JsonProperty("usbVID") int usbVID,
- @JsonProperty("usbPID") int usbPID) {
+ String baseName,
+ String path,
+ String[] otherPaths,
+ CameraType cameraType,
+ int usbVID,
+ int usbPID) {
if (cameraType == CameraType.UsbCamera) {
this.matchedCameraInfo =
PVCameraInfo.fromUsbCameraInfo(
@@ -171,16 +165,16 @@ public class CameraConfiguration {
}
/**
- * Replace a calibration in our list with the same unrotatedImageSize with a new one, or add it if
- * none exists yet. If we are replacing an existing calibration, the old one will be "released"
- * and the underlying data matrices will become invalid.
+ * Replace a calibration in our list with the same resolution with a new one, or add it if none
+ * exists yet. If we are replacing an existing calibration, the old one will be "released" and the
+ * underlying data matrices will become invalid.
*
* @param calibration The calibration to add.
*/
public void addCalibration(CameraCalibrationCoefficients calibration) {
- logger.info("adding calibration " + calibration.unrotatedImageSize);
+ logger.info("adding calibration " + calibration.resolution);
calibrations.stream()
- .filter(it -> it.unrotatedImageSize.equals(calibration.unrotatedImageSize))
+ .filter(it -> it.resolution.equals(calibration.resolution))
.findAny()
.ifPresent(
(it) -> {
@@ -194,12 +188,12 @@ public class CameraConfiguration {
* Remove a calibration from our list. If found, the calibration will be "released". If not found,
* no-op.
*
- * @param unrotatedImageSize The resolution to remove.
+ * @param resolution The resolution to remove.
*/
- public void removeCalibration(Size unrotatedImageSize) {
- logger.info("deleting calibration " + unrotatedImageSize);
+ public void removeCalibration(Size resolution) {
+ logger.info("deleting calibration " + resolution);
calibrations.stream()
- .filter(it -> it.unrotatedImageSize.equals(unrotatedImageSize))
+ .filter(it -> it.resolution.equals(resolution))
.findAny()
.ifPresent(
(it) -> {
@@ -215,7 +209,6 @@ public class CameraConfiguration {
*
* This represents our best guess at an immutable path to detect a camera at.
*/
- @JsonIgnore
public String getDevicePath() {
return matchedCameraInfo.uniquePath();
}
diff --git a/photon-core/src/main/java/org/photonvision/common/configuration/ConfigManager.java b/photon-core/src/main/java/org/photonvision/common/configuration/ConfigManager.java
index d8fe57622..86a994315 100644
--- a/photon-core/src/main/java/org/photonvision/common/configuration/ConfigManager.java
+++ b/photon-core/src/main/java/org/photonvision/common/configuration/ConfigManager.java
@@ -17,7 +17,10 @@
package org.photonvision.common.configuration;
+import io.avaje.json.JsonException;
+import io.avaje.jsonb.Jsonb;
import java.io.File;
+import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -34,7 +37,6 @@ import org.opencv.core.Size;
import org.photonvision.common.logging.LogGroup;
import org.photonvision.common.logging.Logger;
import org.photonvision.common.util.file.FileUtils;
-import org.photonvision.common.util.file.JacksonUtils;
import org.photonvision.vision.processes.VisionSource;
import org.zeroturnaround.zip.ZipUtil;
@@ -233,14 +235,15 @@ public class ConfigManager {
Path.of(getModelsDirectory().toString(), "photonvision-object-detection-models.json")
.toFile();
try {
- JacksonUtils.serialize(
- tempProperties.toPath(), this.getConfig().neuralNetworkPropertyManager());
+ Jsonb.instance()
+ .type(NeuralNetworkModelsSettings.class)
+ .toJson(this.getConfig().getNeuralNetworkProperties(), new FileWriter(tempProperties));
ZipUtil.pack(getModelsDirectory(), out);
// Now delete the tempProperties
if (tempProperties.exists()) {
Files.delete(tempProperties.toPath());
}
- } catch (Exception e) {
+ } catch (IOException | IllegalStateException | JsonException e) {
e.printStackTrace();
}
return out;
diff --git a/photon-core/src/main/java/org/photonvision/common/configuration/HardwareConfig.java b/photon-core/src/main/java/org/photonvision/common/configuration/HardwareConfig.java
index 23d875bfa..feded462c 100644
--- a/photon-core/src/main/java/org/photonvision/common/configuration/HardwareConfig.java
+++ b/photon-core/src/main/java/org/photonvision/common/configuration/HardwareConfig.java
@@ -17,47 +17,49 @@
package org.photonvision.common.configuration;
-import com.fasterxml.jackson.annotation.JsonAlias;
-import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import io.avaje.jsonb.Json;
import java.util.ArrayList;
+import java.util.List;
import org.photonvision.common.hardware.statusLED.StatusLEDType;
-@JsonIgnoreProperties(ignoreUnknown = true)
+@Json
public class HardwareConfig {
- public final String deviceName;
+ public String deviceName;
// LED control
- public final ArrayList ledPins;
- public final boolean ledsCanDim;
- public final ArrayList ledBrightnessRange;
- public final int ledPWMFrequency;
- public final StatusLEDType statusLEDType;
+ public List ledPins;
+ public boolean ledsCanDim;
+ public List ledBrightnessRange;
+ public int ledPWMFrequency;
+ public StatusLEDType statusLEDType;
- @JsonAlias("statusRGBPins")
- public final ArrayList statusLEDPins;
+ // MIGRATION: 2026
+ @Json.Alias("statusRGBPins")
+ public List statusLEDPins;
- @JsonAlias("statusRGBActiveHigh")
- public final boolean statusLEDActiveHigh;
+ // MIGRATION: 2026
+ @Json.Alias("statusRGBActiveHigh")
+ public boolean statusLEDActiveHigh;
// Custom GPIO
- public final String getGPIOCommand;
- public final String setGPIOCommand;
- public final String setPWMCommand;
- public final String setPWMFrequencyCommand;
- public final String releaseGPIOCommand;
+ public String getGPIOCommand;
+ public String setGPIOCommand;
+ public String setPWMCommand;
+ public String setPWMFrequencyCommand;
+ public String releaseGPIOCommand;
// Device stuff
- public final String restartHardwareCommand;
- public final double vendorFOV; // -1 for unmanaged
+ public String restartHardwareCommand;
+ public double vendorFOV; // -1 for unmanaged
public HardwareConfig(
String deviceName,
- ArrayList ledPins,
+ List ledPins,
boolean ledsCanDim,
- ArrayList ledBrightnessRange,
+ List ledBrightnessRange,
int ledPwmFrequency,
StatusLEDType statusLEDType,
- ArrayList statusLEDPins,
+ List statusLEDPins,
boolean statusLEDActiveHigh,
String getGPIOCommand,
String setGPIOCommand,
diff --git a/photon-core/src/main/java/org/photonvision/common/configuration/LegacyConfigProvider.java b/photon-core/src/main/java/org/photonvision/common/configuration/LegacyConfigProvider.java
index 5b5197256..9b93ff832 100644
--- a/photon-core/src/main/java/org/photonvision/common/configuration/LegacyConfigProvider.java
+++ b/photon-core/src/main/java/org/photonvision/common/configuration/LegacyConfigProvider.java
@@ -17,8 +17,11 @@
package org.photonvision.common.configuration;
-import com.fasterxml.jackson.core.JsonProcessingException;
+import io.avaje.json.JsonException;
+import io.avaje.jsonb.Jsonb;
import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
@@ -34,9 +37,6 @@ import java.util.stream.Stream;
import org.photonvision.common.logging.LogGroup;
import org.photonvision.common.logging.Logger;
import org.photonvision.common.util.file.FileUtils;
-import org.photonvision.common.util.file.JacksonUtils;
-import org.photonvision.vision.pipeline.CVPipelineSettings;
-import org.photonvision.vision.pipeline.DriverModePipelineSettings;
import org.photonvision.vision.processes.VisionSource;
import org.wpilib.vision.apriltag.AprilTagFieldLayout;
import org.wpilib.vision.apriltag.AprilTagFields;
@@ -126,14 +126,13 @@ class LegacyConfigProvider extends ConfigProvider {
AprilTagFieldLayout atfl = null;
if (hardwareConfigFile.exists()) {
- try {
- hardwareConfig =
- JacksonUtils.deserialize(hardwareConfigFile.toPath(), HardwareConfig.class);
+ try (var stream = new FileInputStream(hardwareConfigFile)) {
+ hardwareConfig = Jsonb.instance().type(HardwareConfig.class).fromJson(stream);
if (hardwareConfig == null) {
logger.error("Could not deserialize hardware config! Loading defaults");
hardwareConfig = new HardwareConfig();
}
- } catch (IOException e) {
+ } catch (IOException | IllegalStateException | JsonException e) {
logger.error("Could not deserialize hardware config! Loading defaults");
hardwareConfig = new HardwareConfig();
}
@@ -143,14 +142,13 @@ class LegacyConfigProvider extends ConfigProvider {
}
if (hardwareSettingsFile.exists()) {
- try {
- hardwareSettings =
- JacksonUtils.deserialize(hardwareSettingsFile.toPath(), HardwareSettings.class);
+ try (var stream = new FileInputStream(hardwareSettingsFile)) {
+ hardwareSettings = Jsonb.instance().type(HardwareSettings.class).fromJson(stream);
if (hardwareSettings == null) {
logger.error("Could not deserialize hardware settings! Loading defaults");
hardwareSettings = new HardwareSettings();
}
- } catch (IOException e) {
+ } catch (IOException | IllegalStateException | JsonException e) {
logger.error("Could not deserialize hardware settings! Loading defaults");
hardwareSettings = new HardwareSettings();
}
@@ -160,13 +158,13 @@ class LegacyConfigProvider extends ConfigProvider {
}
if (networkConfigFile.exists()) {
- try {
- networkConfig = JacksonUtils.deserialize(networkConfigFile.toPath(), NetworkConfig.class);
+ try (var stream = new FileInputStream(networkConfigFile)) {
+ networkConfig = Jsonb.instance().type(NetworkConfig.class).fromJson(stream);
if (networkConfig == null) {
logger.error("Could not deserialize network config! Loading defaults");
networkConfig = new NetworkConfig();
}
- } catch (IOException e) {
+ } catch (IOException | IllegalStateException | JsonException e) {
logger.error("Could not deserialize network config! Loading defaults");
networkConfig = new NetworkConfig();
}
@@ -184,13 +182,12 @@ class LegacyConfigProvider extends ConfigProvider {
}
if (apriltagFieldLayoutFile.exists()) {
- try {
- atfl =
- JacksonUtils.deserialize(apriltagFieldLayoutFile.toPath(), AprilTagFieldLayout.class);
+ try (var stream = new FileInputStream(apriltagFieldLayoutFile)) {
+ atfl = Jsonb.instance().type(AprilTagFieldLayout.class).fromJson(stream);
if (atfl == null) {
logger.error("Could not deserialize apriltag field layout! (still null)");
}
- } catch (IOException e) {
+ } catch (IOException | IllegalStateException | JsonException e) {
logger.error("Could not deserialize apriltag field layout!", e);
atfl = null; // not required, nice to be explicit
}
@@ -227,14 +224,14 @@ class LegacyConfigProvider extends ConfigProvider {
// Delete old configs
FileUtils.deleteDirectory(camerasFolder.toPath());
- try {
- JacksonUtils.serialize(networkConfigFile.toPath(), config.getNetworkConfig());
- } catch (IOException e) {
+ try (var stream = new FileOutputStream(networkConfigFile)) {
+ Jsonb.instance().type(NetworkConfig.class).toJson(config.getNetworkConfig(), stream);
+ } catch (IOException | IllegalStateException | JsonException e) {
logger.error("Could not save network config!", e);
}
- try {
- JacksonUtils.serialize(hardwareSettingsFile.toPath(), config.getHardwareSettings());
- } catch (IOException e) {
+ try (var stream = new FileOutputStream(hardwareSettingsFile)) {
+ Jsonb.instance().type(HardwareSettings.class).toJson(config.getHardwareSettings(), stream);
+ } catch (IOException | IllegalStateException | JsonException e) {
logger.error("Could not save hardware config!", e);
}
@@ -249,33 +246,11 @@ class LegacyConfigProvider extends ConfigProvider {
subdir.toFile().mkdirs();
}
- try {
- JacksonUtils.serialize(Path.of(subdir.toString(), "config.json"), camConfig);
- } catch (IOException e) {
+ try (var stream = new FileOutputStream(Path.of(subdir.toString(), "config.json").toFile())) {
+ Jsonb.instance().type(CameraConfiguration.class).toJson(camConfig, stream);
+ } catch (IOException | IllegalStateException | JsonException e) {
logger.error("Could not save config.json for " + subdir, e);
}
-
- try {
- JacksonUtils.serialize(
- Path.of(subdir.toString(), "drivermode.json"), camConfig.driveModeSettings);
- } catch (IOException e) {
- logger.error("Could not save drivermode.json for " + subdir, e);
- }
-
- for (var pipe : camConfig.pipelineSettings) {
- var pipePath = Path.of(subdir.toString(), "pipelines", pipe.pipelineNickname + ".json");
-
- if (!pipePath.getParent().toFile().exists()) {
- // TODO: check for error
- pipePath.getParent().toFile().mkdirs();
- }
-
- try {
- JacksonUtils.serialize(pipePath, pipe);
- } catch (IOException e) {
- logger.error("Could not save " + pipe.pipelineNickname + ".json!", e);
- }
- }
}
logger.info("Settings saved!");
return false; // TODO, deal with this. Do I need to?
@@ -289,11 +264,9 @@ class LegacyConfigProvider extends ConfigProvider {
for (var subdir : subdirectories) {
var cameraConfigPath = Path.of(subdir.toString(), "config.json");
CameraConfiguration loadedConfig = null;
- try {
- loadedConfig =
- JacksonUtils.deserialize(
- cameraConfigPath.toAbsolutePath(), CameraConfiguration.class);
- } catch (JsonProcessingException e) {
+ try (var stream = new FileInputStream(cameraConfigPath.toFile())) {
+ loadedConfig = Jsonb.instance().type(CameraConfiguration.class).fromJson(stream);
+ } catch (IllegalStateException | JsonException e) {
logger.error("Camera config deserialization failed!", e);
e.printStackTrace();
}
@@ -302,63 +275,6 @@ class LegacyConfigProvider extends ConfigProvider {
continue; // TODO how do we later try to load this camera if it gets reconnected?
}
- // At this point we have only loaded the base stuff
- // We still need to deserialize pipelines, as well as
- // driver mode settings
- var driverModeFile = Path.of(subdir.toString(), "drivermode.json");
- DriverModePipelineSettings driverMode;
- try {
- driverMode =
- JacksonUtils.deserialize(
- driverModeFile.toAbsolutePath(), DriverModePipelineSettings.class);
- } catch (JsonProcessingException e) {
- logger.error("Could not deserialize drivermode.json! Loading defaults");
- logger.debug(Arrays.toString(e.getStackTrace()));
- driverMode = new DriverModePipelineSettings();
- }
- if (driverMode == null) {
- logger.warn(
- "Could not load camera " + subdir + "'s drivermode.json! Loading" + " default");
- driverMode = new DriverModePipelineSettings();
- }
-
- // Load pipelines by mapping the files within the pipelines subdir
- // to their deserialized equivalents
- var pipelineSubdirectory = Path.of(subdir.toString(), "pipelines");
- List settings = Collections.emptyList();
- if (pipelineSubdirectory.toFile().exists()) {
- try (Stream subdirectoryFiles = Files.list(pipelineSubdirectory)) {
- settings =
- subdirectoryFiles
- .filter(p -> p.toFile().isFile())
- .map(
- p -> {
- var relativizedFilePath =
- configDirectoryFile
- .toPath()
- .toAbsolutePath()
- .relativize(p)
- .toString();
- try {
- return JacksonUtils.deserialize(p, CVPipelineSettings.class);
- } catch (JsonProcessingException e) {
- logger.error("Exception while deserializing " + relativizedFilePath, e);
- } catch (IOException e) {
- logger.warn(
- "Could not load pipeline at "
- + relativizedFilePath
- + "! Skipping...");
- }
- return null;
- })
- .filter(Objects::nonNull)
- .toList();
- }
- }
-
- loadedConfig.driveModeSettings = driverMode;
- loadedConfig.addPipelineSettings(settings);
-
loadedConfigurations.put(subdir.toFile().getName(), loadedConfig);
}
} catch (IOException e) {
diff --git a/photon-core/src/main/java/org/photonvision/common/configuration/NetworkConfig.java b/photon-core/src/main/java/org/photonvision/common/configuration/NetworkConfig.java
index bb0c6cd05..cd23ae65e 100644
--- a/photon-core/src/main/java/org/photonvision/common/configuration/NetworkConfig.java
+++ b/photon-core/src/main/java/org/photonvision/common/configuration/NetworkConfig.java
@@ -17,16 +17,17 @@
package org.photonvision.common.configuration;
-import com.fasterxml.jackson.annotation.JsonAlias;
-import com.fasterxml.jackson.annotation.JsonCreator;
-import com.fasterxml.jackson.annotation.JsonIgnore;
-import com.fasterxml.jackson.annotation.JsonProperty;
+import io.avaje.jsonb.Json;
import org.photonvision.common.hardware.Platform;
import org.photonvision.common.networking.NetworkMode;
+@Json
public class NetworkConfig {
// Can be an integer team number, or an IP address
+ // MIGRATION: 2023
+ @Json.Alias("teamNumber")
public String ntServerAddress = "0";
+
public NetworkMode connectionType = NetworkMode.DHCP;
public String staticIp = "";
public String hostname = "photonvision";
@@ -34,8 +35,8 @@ public class NetworkConfig {
public boolean shouldManage;
public boolean shouldPublishProto = false;
- @JsonIgnore public static final String NM_IFACE_STRING = "${interface}";
- @JsonIgnore public static final String NM_IP_STRING = "${ipaddr}";
+ public static final String NM_IFACE_STRING = "${interface}";
+ public static final String NM_IP_STRING = "${ipaddr}";
public String networkManagerIface = "";
// TODO: remove these strings if no longer needed
@@ -50,19 +51,17 @@ public class NetworkConfig {
setShouldManage(deviceCanManageNetwork());
}
- @JsonCreator
public NetworkConfig(
- @JsonProperty("ntServerAddress") @JsonAlias({"ntServerAddress", "teamNumber"})
- String ntServerAddress,
- @JsonProperty("connectionType") NetworkMode connectionType,
- @JsonProperty("staticIp") String staticIp,
- @JsonProperty("hostname") String hostname,
- @JsonProperty("runNTServer") boolean runNTServer,
- @JsonProperty("shouldManage") boolean shouldManage,
- @JsonProperty("shouldPublishProto") boolean shouldPublishProto,
- @JsonProperty("networkManagerIface") String networkManagerIface,
- @JsonProperty("setStaticCommand") String setStaticCommand,
- @JsonProperty("setDHCPcommand") String setDHCPcommand) {
+ String ntServerAddress,
+ NetworkMode connectionType,
+ String staticIp,
+ String hostname,
+ boolean runNTServer,
+ boolean shouldManage,
+ boolean shouldPublishProto,
+ String networkManagerIface,
+ String setStaticCommand,
+ String setDHCPcommand) {
this.ntServerAddress = ntServerAddress;
this.connectionType = connectionType;
this.staticIp = staticIp;
@@ -89,12 +88,10 @@ public class NetworkConfig {
config.setDHCPcommand);
}
- @JsonIgnore
public String getPhysicalInterfaceName() {
return this.networkManagerIface;
}
- @JsonIgnore
public String getEscapedInterfaceName() {
return "\"" + networkManagerIface + "\"";
}
@@ -103,7 +100,6 @@ public class NetworkConfig {
this.shouldManage = shouldManage && this.deviceCanManageNetwork();
}
- @JsonIgnore
protected boolean deviceCanManageNetwork() {
return Platform.isLinux();
}
diff --git a/photon-core/src/main/java/org/photonvision/common/configuration/NeuralNetworkModelManager.java b/photon-core/src/main/java/org/photonvision/common/configuration/NeuralNetworkModelManager.java
index b1df50fa4..9700b5539 100644
--- a/photon-core/src/main/java/org/photonvision/common/configuration/NeuralNetworkModelManager.java
+++ b/photon-core/src/main/java/org/photonvision/common/configuration/NeuralNetworkModelManager.java
@@ -318,7 +318,7 @@ public class NeuralNetworkModelManager {
}
ModelProperties properties =
- ConfigManager.getInstance().getConfig().neuralNetworkPropertyManager().getModel(path);
+ ConfigManager.getInstance().getConfig().getNeuralNetworkProperties().getModel(path);
if (properties == null) {
logger.warn(
@@ -332,7 +332,7 @@ public class NeuralNetworkModelManager {
// NeuralNetworkModelsSettings
ConfigManager.getInstance()
.getConfig()
- .neuralNetworkPropertyManager()
+ .getNeuralNetworkProperties()
.addModelProperties(properties);
} catch (IllegalArgumentException | IOException e) {
logger.error("Failed to translate legacy model filename to properties: " + path, e);
@@ -486,7 +486,7 @@ public class NeuralNetworkModelManager {
.getConfig()
.setNeuralNetworkProperties(
supportedProperties.sum(
- ConfigManager.getInstance().getConfig().neuralNetworkPropertyManager()));
+ ConfigManager.getInstance().getConfig().getNeuralNetworkProperties()));
}
public boolean clearModels() {
@@ -511,7 +511,7 @@ public class NeuralNetworkModelManager {
}
// Delete model info
- return ConfigManager.getInstance().getConfig().neuralNetworkPropertyManager().clear();
+ return ConfigManager.getInstance().getConfig().getNeuralNetworkProperties().clear();
}
public File exportSingleModel(String modelPath) {
@@ -525,7 +525,7 @@ public class NeuralNetworkModelManager {
ModelProperties properties =
ConfigManager.getInstance()
.getConfig()
- .neuralNetworkPropertyManager()
+ .getNeuralNetworkProperties()
.getModel(Path.of(modelPath));
String fileName = "";
diff --git a/photon-core/src/main/java/org/photonvision/common/configuration/NeuralNetworkModelsSettings.java b/photon-core/src/main/java/org/photonvision/common/configuration/NeuralNetworkModelsSettings.java
index 56e230280..96b69698f 100644
--- a/photon-core/src/main/java/org/photonvision/common/configuration/NeuralNetworkModelsSettings.java
+++ b/photon-core/src/main/java/org/photonvision/common/configuration/NeuralNetworkModelsSettings.java
@@ -17,9 +17,10 @@
package org.photonvision.common.configuration;
-import com.fasterxml.jackson.annotation.JsonCreator;
-import com.fasterxml.jackson.annotation.JsonIgnore;
-import com.fasterxml.jackson.annotation.JsonProperty;
+import io.avaje.jsonb.Json;
+import io.avaje.jsonb.JsonType;
+import io.avaje.jsonb.Jsonb;
+import io.avaje.jsonb.Types;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -27,27 +28,29 @@ import java.nio.file.Paths;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
import org.photonvision.common.configuration.NeuralNetworkModelManager.Family;
import org.photonvision.common.configuration.NeuralNetworkModelManager.Version;
+@Json
public class NeuralNetworkModelsSettings {
/*
* The properties of the model. This is used to determine which model to load.
* The only families currently supported are RKNN and Rubik (custom .tflite)
*/
+ @Json
public record ModelProperties(
- @JsonProperty("modelPath") Path modelPath,
- @JsonProperty("nickname") String nickname,
- @JsonProperty("labels") List labels,
- @JsonProperty("resolutionWidth") int resolutionWidth,
- @JsonProperty("resolutionHeight") int resolutionHeight,
- @JsonProperty("family") Family family,
- @JsonProperty("version") Version version) {
- @JsonCreator
- public ModelProperties {}
-
+ Path modelPath,
+ String nickname,
+ List labels,
+ int resolutionWidth,
+ int resolutionHeight,
+ Family family,
+ Version version) {
ModelProperties(ModelProperties other) {
this(
other.modelPath,
@@ -59,13 +62,6 @@ public class NeuralNetworkModelsSettings {
other.version);
}
- // In v2025.3.1, this was single string for the model path. but the first argument
- // is now nickname
- public ModelProperties(@JsonProperty("nickname") String filename)
- throws IllegalArgumentException, IOException {
- this(createFromFilename(filename));
- }
-
// ============= Migration code from v2025.3.1 ===========
private static Pattern modelPattern =
@@ -160,25 +156,58 @@ public class NeuralNetworkModelsSettings {
// The path to the model is used as the key in the map because it is unique to
// the model, and should not change
- @JsonProperty("modelPathToProperties")
+ @Json.Ignore
private HashMap modelPathToProperties =
new HashMap();
/**
* Constructor for the NeuralNetworkProperties class.
*
- * This object holds a LinkedList of {@link ModelProperties} objects
+ *
This object holds a HashMap of {@link ModelProperties} objects
*/
public NeuralNetworkModelsSettings() {}
/**
* Constructor for the NeuralNetworkProperties class.
*
- *
This object holds a LinkedList of {@link ModelProperties} objects.
+ *
This object holds a HashMap of {@link ModelProperties} objects.
+ *
+ * @param modelPropertiesMap When the class is constructed, it will hold the provided map
+ */
+ public NeuralNetworkModelsSettings(HashMap modelPropertiesMap) {
+ modelPathToProperties = modelPropertiesMap;
+ }
+
+ /**
+ * Constructor for the NeuralNetworkProperties class.
+ *
+ * This object holds a HashMap of {@link ModelProperties} objects.
*
* @param modelPropertiesList When the class is constructed, it will hold the provided list
*/
- public NeuralNetworkModelsSettings(HashMap modelPropertiesList) {}
+ @Json.Creator
+ public NeuralNetworkModelsSettings(
+ ModelProperties[] models, @Json.Unmapped Map unmapped) {
+ JsonType> modelPropsMapJsonb =
+ Jsonb.instance().type(Types.mapOf(ModelProperties.class));
+ Stream modelPropsStream;
+ if (models != null) {
+ modelPropsStream = Arrays.stream(models);
+ } else if (unmapped.containsKey("modelPathToProperties")) {
+ // MIGRATION: 2026
+ modelPropsStream =
+ modelPropsMapJsonb.fromObject(unmapped.get("modelPathToProperties")).values().stream();
+ } else {
+ modelPropsStream = Stream.empty();
+ }
+ this(
+ modelPropsStream.collect(
+ Collectors.toMap(
+ (model) -> model.modelPath(),
+ (model) -> model,
+ (prev, next) -> next,
+ HashMap::new)));
+ }
@Override
public String toString() {
@@ -239,7 +268,7 @@ public class NeuralNetworkModelsSettings {
*
* @return A list of all models
*/
- @JsonIgnore
+ @Json.Property("models")
public ModelProperties[] getModels() {
return modelPathToProperties.values().toArray(new ModelProperties[0]);
}
diff --git a/photon-core/src/main/java/org/photonvision/common/configuration/PhotonConfiguration.java b/photon-core/src/main/java/org/photonvision/common/configuration/PhotonConfiguration.java
index 83ece829c..bf9c8f28f 100644
--- a/photon-core/src/main/java/org/photonvision/common/configuration/PhotonConfiguration.java
+++ b/photon-core/src/main/java/org/photonvision/common/configuration/PhotonConfiguration.java
@@ -17,19 +17,25 @@
package org.photonvision.common.configuration;
+import io.avaje.jsonb.Json;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import org.photonvision.vision.processes.VisionSource;
import org.wpilib.vision.apriltag.AprilTagFieldLayout;
+@Json
public class PhotonConfiguration {
private final HardwareConfig hardwareConfig;
private final HardwareSettings hardwareSettings;
private NetworkConfig networkConfig;
- private AprilTagFieldLayout atfl;
+
+ @Json.Property("atfl")
+ private AprilTagFieldLayout aprilTagFieldLayout;
+
private NeuralNetworkModelsSettings neuralNetworkProperties;
- private HashMap cameraConfigurations;
+ private Map cameraConfigurations;
public PhotonConfiguration(
HardwareConfig hardwareConfig,
@@ -46,19 +52,20 @@ public class PhotonConfiguration {
new HashMap<>());
}
+ @Json.Creator
public PhotonConfiguration(
HardwareConfig hardwareConfig,
HardwareSettings hardwareSettings,
NetworkConfig networkConfig,
AprilTagFieldLayout atfl,
NeuralNetworkModelsSettings neuralNetworkProperties,
- HashMap cameraConfigurations) {
+ Map cameraConfigurations) {
this.hardwareConfig = hardwareConfig;
this.hardwareSettings = hardwareSettings;
this.networkConfig = networkConfig;
this.neuralNetworkProperties = neuralNetworkProperties;
this.cameraConfigurations = cameraConfigurations;
- this.atfl = atfl;
+ this.aprilTagFieldLayout = atfl;
}
public PhotonConfiguration() {
@@ -83,15 +90,15 @@ public class PhotonConfiguration {
}
public AprilTagFieldLayout getApriltagFieldLayout() {
- return atfl;
+ return aprilTagFieldLayout;
}
- public NeuralNetworkModelsSettings neuralNetworkPropertyManager() {
+ public NeuralNetworkModelsSettings getNeuralNetworkProperties() {
return neuralNetworkProperties;
}
public void setApriltagFieldLayout(AprilTagFieldLayout atfl) {
- this.atfl = atfl;
+ this.aprilTagFieldLayout = atfl;
}
public void setNetworkConfig(NetworkConfig networkConfig) {
@@ -102,7 +109,7 @@ public class PhotonConfiguration {
this.neuralNetworkProperties = neuralNetworkProperties;
}
- public HashMap getCameraConfigurations() {
+ public Map getCameraConfigurations() {
return cameraConfigurations;
}
@@ -148,8 +155,8 @@ public class PhotonConfiguration {
+ hardwareSettings
+ "\n networkConfig="
+ networkConfig
- + "\n atfl="
- + atfl
+ + "\n aprilTagFieldLayout="
+ + aprilTagFieldLayout
+ "\n neuralNetworkProperties="
+ neuralNetworkProperties
+ "\n cameraConfigurations={"
diff --git a/photon-core/src/main/java/org/photonvision/common/configuration/SqlConfigProvider.java b/photon-core/src/main/java/org/photonvision/common/configuration/SqlConfigProvider.java
index c00e98e33..170fec244 100644
--- a/photon-core/src/main/java/org/photonvision/common/configuration/SqlConfigProvider.java
+++ b/photon-core/src/main/java/org/photonvision/common/configuration/SqlConfigProvider.java
@@ -17,24 +17,27 @@
package org.photonvision.common.configuration;
+import io.avaje.json.JsonException;
+import io.avaje.jsonb.JsonType;
+import io.avaje.jsonb.Jsonb;
+import io.avaje.jsonb.Types;
import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.sql.*;
-import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
-import java.util.Objects;
import java.util.function.Supplier;
+import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.photonvision.common.configuration.CameraConfiguration.LegacyCameraConfigStruct;
import org.photonvision.common.configuration.DatabaseSchema.Columns;
import org.photonvision.common.configuration.DatabaseSchema.Tables;
import org.photonvision.common.logging.LogGroup;
import org.photonvision.common.logging.Logger;
-import org.photonvision.common.util.file.JacksonUtils;
+import org.photonvision.vision.camera.PVCameraInfo;
import org.photonvision.vision.pipeline.CVPipelineSettings;
import org.photonvision.vision.pipeline.DriverModePipelineSettings;
import org.wpilib.vision.apriltag.AprilTagFieldLayout;
@@ -260,16 +263,16 @@ public class SqlConfigProvider extends ConfigProvider {
T configObj;
if (!configString.isBlank()) {
try {
- configObj = JacksonUtils.deserialize(configString, ref);
+ configObj = Jsonb.instance().type(ref).fromJson(configString);
logger.info("Loaded " + ref.getSimpleName() + " from database");
return configObj;
- } catch (IOException e) {
+ } catch (IllegalStateException | JsonException e) {
logger.error("Could not deserialize " + ref.getSimpleName() + " from database!", e);
}
} else {
logger.debug("No " + ref.getSimpleName() + " in database");
}
- // either the config entry is empty or Jackson threw an exception
+ // either the config entry is empty or Jsonb threw an exception
try {
configObj = factory.get();
logger.info("Loaded default " + ref.getSimpleName());
@@ -390,30 +393,16 @@ public class SqlConfigProvider extends ConfigProvider {
var config = c.getValue();
statement.setString(1, c.getKey());
- statement.setString(2, JacksonUtils.serializeToString(config));
- statement.setString(3, JacksonUtils.serializeToString(config.driveModeSettings));
+ statement.setString(2, Jsonb.instance().type(CameraConfiguration.class).toJson(config));
- // Serializing a list of abstract classes sucks. Instead, make it into an array
- // of strings, which we can later unpack back into individual settings
- List settings =
- config.pipelineSettings.stream()
- .map(
- it -> {
- try {
- return JacksonUtils.serializeToString(it);
- } catch (IOException e) {
- e.printStackTrace();
- return null;
- }
- })
- .filter(Objects::nonNull)
- .toList();
- statement.setString(4, JacksonUtils.serializeToString(settings));
+ // MIGRATION: 2026
+ // We used to serialize pipelines separately, but don't anymore
+ statement.setString(3, "null");
+ statement.setString(4, "[]");
statement.executeUpdate();
}
-
- } catch (SQLException | IOException e) {
+ } catch (SQLException | IllegalStateException | JsonException e) {
logger.error("Err saving cameras", e);
try {
conn.rollback();
@@ -469,7 +458,7 @@ public class SqlConfigProvider extends ConfigProvider {
addFile(
statement1,
GlobalKeys.HARDWARE_SETTINGS,
- JacksonUtils.serializeToString(config.getHardwareSettings()));
+ Jsonb.instance().type(HardwareSettings.class).toJson(config.getHardwareSettings()));
statement1.executeUpdate();
}
@@ -478,7 +467,7 @@ public class SqlConfigProvider extends ConfigProvider {
addFile(
statement2,
GlobalKeys.NETWORK_CONFIG,
- JacksonUtils.serializeToString(config.getNetworkConfig()));
+ Jsonb.instance().type(NetworkConfig.class).toJson(config.getNetworkConfig()));
statement2.executeUpdate();
statement2.close();
}
@@ -488,7 +477,7 @@ public class SqlConfigProvider extends ConfigProvider {
addFile(
statement3,
GlobalKeys.HARDWARE_CONFIG,
- JacksonUtils.serializeToString(config.getHardwareConfig()));
+ Jsonb.instance().type(HardwareConfig.class).toJson(config.getHardwareConfig()));
statement3.executeUpdate();
statement3.close();
}
@@ -498,12 +487,14 @@ public class SqlConfigProvider extends ConfigProvider {
addFile(
statement3,
GlobalKeys.NEURAL_NETWORK_PROPERTIES,
- JacksonUtils.serializeToString(config.neuralNetworkPropertyManager()));
+ Jsonb.instance()
+ .type(NeuralNetworkModelsSettings.class)
+ .toJson(config.getNeuralNetworkProperties()));
statement3.executeUpdate();
statement3.close();
}
- } catch (SQLException | IOException e) {
+ } catch (SQLException | IllegalStateException | JsonException e) {
logger.error("Err saving global", e);
try {
conn.rollback();
@@ -594,6 +585,12 @@ public class SqlConfigProvider extends ConfigProvider {
private HashMap loadCameraConfigs(Connection conn) {
HashMap loadedConfigurations = new HashMap<>();
+ // MIGRATION: 2026
+ // This is designed to always match for efficiency reasons, so that the whole camera config
+ // isn't scanned. The second capture group determines if the camera info is in the old or new
+ // format
+ final var cameraInfoPattern = Pattern.compile("\"(PV[\\w.]*CameraInfo)\"\\s*(:?)");
+
// Query every single row of the cameras db
PreparedStatement query = null;
try {
@@ -614,57 +611,82 @@ public class SqlConfigProvider extends ConfigProvider {
while (result.next()) {
String uniqueName = "";
try {
- List dummyList = new ArrayList<>();
+ JsonType> strListJsonb = Jsonb.instance().type(Types.listOf(String.class));
uniqueName = result.getString(Columns.CAM_UNIQUE_NAME);
// A horrifying hack to keep backward compat with otherpaths
// We -really- need to delete this -stupid- otherpaths column. I hate it.
- var configStr = result.getString(Columns.CAM_CONFIG_JSON);
- CameraConfiguration config =
- JacksonUtils.deserialize(configStr, CameraConfiguration.class);
+ // MIGRATION: 2024
+ var configJson = result.getString(Columns.CAM_CONFIG_JSON);
+ // MIGRATION: 2026
+ var cameraInfoMatcher = cameraInfoPattern.matcher(configJson);
+ if (cameraInfoMatcher.find() && cameraInfoMatcher.group(2).equals(":")) {
+ logger.info("Legacy type-wrapper PVCameraInfo being migrated");
+ configJson = PVCameraInfo.remapConfigJson(configJson, cameraInfoMatcher.group(1));
+ }
+
+ CameraConfiguration config =
+ Jsonb.instance().type(CameraConfiguration.class).fromJson(configJson);
+
+ // MIGRATION: 2024
if (config.matchedCameraInfo == null) {
logger.info("Legacy CameraConfiguration detected - upgrading");
// manually create the matchedCameraInfo ourselves. Need to upgrade:
// baseName, path, otherPaths, cameraType, usbvid/pid -> matchedCameraInfo
config.matchedCameraInfo =
- JacksonUtils.deserialize(configStr, LegacyCameraConfigStruct.class)
+ Jsonb.instance()
+ .type(LegacyCameraConfigStruct.class)
+ .fromJson(configJson)
.matchedCameraInfo;
// Except that otherPaths used to be its own column. so hack that in here as well
var otherPaths =
- JacksonUtils.deserialize(
- result.getString(Columns.CAM_OTHERPATHS_JSON), String[].class);
+ Jsonb.instance()
+ .type(String[].class)
+ .fromJson(result.getString(Columns.CAM_OTHERPATHS_JSON));
if (config.matchedCameraInfo instanceof UsbCameraInfo usbInfo) {
usbInfo.otherPaths = otherPaths;
}
}
- var driverMode =
- JacksonUtils.deserialize(
- result.getString(Columns.CAM_DRIVERMODE_JSON), DriverModePipelineSettings.class);
- List> pipelineSettings =
- JacksonUtils.deserialize(
- result.getString(Columns.CAM_PIPELINE_JSONS), dummyList.getClass());
+ // MIGRATION: 2026
+ List legacyPipelineSettings =
+ strListJsonb.fromJson(result.getString(Columns.CAM_PIPELINE_JSONS));
- List loadedSettings = new ArrayList<>();
- for (var setting : pipelineSettings) {
- if (setting instanceof String str) {
- try {
- loadedSettings.add(JacksonUtils.deserialize(str, CVPipelineSettings.class));
- } catch (IOException e) {
- logger.error(
- "Could not deserialize pipeline setting for camera " + config.nickname, e);
- }
+ for (var pipelineJson : legacyPipelineSettings) {
+ logger.info("Importing pipeline JSON into camera settings");
+ if (pipelineJson.startsWith("[")) {
+ logger.info("Legacy type-wrapper CVPipelineSettings being migrated");
+ pipelineJson = CVPipelineSettings.remapSettingsJson(pipelineJson);
+ }
+
+ try {
+ config.pipelineSettings.add(
+ Jsonb.instance().type(CVPipelineSettings.class).fromJson(pipelineJson));
+ } catch (IllegalStateException | JsonException e) {
+ logger.error(
+ "Could not deserialize pipeline setting for camera " + config.nickname, e);
}
}
- config.pipelineSettings = loadedSettings;
- config.driveModeSettings = driverMode;
+ // MIGRATION: 2026
+ if (config.driveModeSettings == null) {
+ logger.info("Importing driver mode JSON into camera settings");
+ var driverModeJson = result.getString(Columns.CAM_DRIVERMODE_JSON);
+ if (driverModeJson.startsWith("[")) {
+ logger.info("Legacy type-wrapper CVPipelineSettings being migrated");
+ driverModeJson = CVPipelineSettings.remapSettingsJson(driverModeJson);
+ }
+
+ config.driveModeSettings =
+ Jsonb.instance().type(DriverModePipelineSettings.class).fromJson(driverModeJson);
+ }
+
loadedConfigurations.put(uniqueName, config);
- } catch (IOException e) {
+ } catch (IllegalStateException | JsonException e) {
logger.error(
"Could not deserialize camera configuration " + uniqueName + " from database!", e);
}
diff --git a/photon-core/src/main/java/org/photonvision/common/dataflow/DataChangeSubscriber.java b/photon-core/src/main/java/org/photonvision/common/dataflow/DataChangeSubscriber.java
index 92a06fdb6..413a1e27b 100644
--- a/photon-core/src/main/java/org/photonvision/common/dataflow/DataChangeSubscriber.java
+++ b/photon-core/src/main/java/org/photonvision/common/dataflow/DataChangeSubscriber.java
@@ -46,7 +46,7 @@ public abstract class DataChangeSubscriber {
this(DataChangeSource.AllSources, wantedDestinations);
}
- public abstract void onDataChangeEvent(DataChangeEvent> event);
+ public abstract void onDataChangeEvent(DataChangeEvent event);
@Override
public int hashCode() {
diff --git a/photon-core/src/main/java/org/photonvision/common/dataflow/networktables/NetworkTablesManager.java b/photon-core/src/main/java/org/photonvision/common/dataflow/networktables/NetworkTablesManager.java
index 5f0e37ba7..0e30e5996 100644
--- a/photon-core/src/main/java/org/photonvision/common/dataflow/networktables/NetworkTablesManager.java
+++ b/photon-core/src/main/java/org/photonvision/common/dataflow/networktables/NetworkTablesManager.java
@@ -17,10 +17,12 @@
package org.photonvision.common.dataflow.networktables;
-import java.io.IOException;
+import io.avaje.json.JsonException;
+import io.avaje.jsonb.Jsonb;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.HashMap;
+import java.util.Map;
import org.photonvision.PhotonVersion;
import org.photonvision.common.configuration.CameraConfiguration;
import org.photonvision.common.configuration.ConfigManager;
@@ -34,7 +36,6 @@ import org.photonvision.common.logging.LogLevel;
import org.photonvision.common.logging.Logger;
import org.photonvision.common.networking.NetworkUtils;
import org.photonvision.common.util.TimedTaskManager;
-import org.photonvision.common.util.file.JacksonUtils;
import org.wpilib.driverstation.Alert;
import org.wpilib.driverstation.Alert.Level;
import org.wpilib.networktables.LogMessage;
@@ -199,7 +200,7 @@ public class NetworkTablesManager {
var atfl_json = event.valueData.value.getString();
try {
System.out.println("Got new field layout!");
- var atfl = JacksonUtils.deserialize(atfl_json, AprilTagFieldLayout.class);
+ var atfl = Jsonb.instance().type(AprilTagFieldLayout.class).fromJson(atfl_json);
ConfigManager.getInstance().getConfig().setApriltagFieldLayout(atfl);
ConfigManager.getInstance().requestSave();
DataChangeService.getInstance()
@@ -207,7 +208,7 @@ public class NetworkTablesManager {
new OutgoingUIEvent<>(
"fullsettings",
UIPhotonConfiguration.programStateToUi(ConfigManager.getInstance().getConfig())));
- } catch (IOException e) {
+ } catch (IllegalStateException | JsonException e) {
logger.error("Error deserializing atfl!");
logger.error(atfl_json);
}
@@ -270,7 +271,7 @@ public class NetworkTablesManager {
return;
}
- HashMap cameraConfigs =
+ Map cameraConfigs =
ConfigManager.getInstance().getConfig().getCameraConfigurations();
String[] cameraNames =
cameraConfigs.entrySet().stream()
diff --git a/photon-core/src/main/java/org/photonvision/common/dataflow/websocket/UICameraConfiguration.java b/photon-core/src/main/java/org/photonvision/common/dataflow/websocket/UICameraConfiguration.java
index 528889884..194992379 100644
--- a/photon-core/src/main/java/org/photonvision/common/dataflow/websocket/UICameraConfiguration.java
+++ b/photon-core/src/main/java/org/photonvision/common/dataflow/websocket/UICameraConfiguration.java
@@ -17,13 +17,15 @@
package org.photonvision.common.dataflow.websocket;
-import java.util.HashMap;
+import io.avaje.jsonb.Json;
import java.util.List;
+import java.util.Map;
import org.photonvision.common.configuration.CameraConfiguration;
import org.photonvision.vision.calibration.UICameraCalibrationCoefficients;
import org.photonvision.vision.camera.PVCameraInfo;
import org.photonvision.vision.camera.QuirkyCamera;
+@Json
public class UICameraConfiguration {
// Path to the camera device. On Linux, this is a special file in /dev/v4l/by-id
// or /dev/videoN.
@@ -37,10 +39,10 @@ public class UICameraConfiguration {
public String uniqueName;
public double fov;
- public HashMap currentPipelineSettings;
+ public Map currentPipelineSettings;
public int currentPipelineIndex;
public List pipelineNicknames;
- public HashMap> videoFormatList;
+ public List> videoFormatList;
public int outputStreamPort;
public int inputStreamPort;
public List calibrations;
diff --git a/photon-core/src/main/java/org/photonvision/common/dataflow/websocket/UIGeneralSettings.java b/photon-core/src/main/java/org/photonvision/common/dataflow/websocket/UIGeneralSettings.java
index 087ec2f74..4646ab49a 100644
--- a/photon-core/src/main/java/org/photonvision/common/dataflow/websocket/UIGeneralSettings.java
+++ b/photon-core/src/main/java/org/photonvision/common/dataflow/websocket/UIGeneralSettings.java
@@ -17,9 +17,11 @@
package org.photonvision.common.dataflow.websocket;
+import io.avaje.jsonb.Json;
import java.util.List;
import org.photonvision.common.configuration.NeuralNetworkModelsSettings;
+@Json
public class UIGeneralSettings {
public UIGeneralSettings(
String version,
diff --git a/photon-core/src/main/java/org/photonvision/common/dataflow/websocket/UILightingConfig.java b/photon-core/src/main/java/org/photonvision/common/dataflow/websocket/UILightingConfig.java
index 57d5579f5..bf2d2e2a1 100644
--- a/photon-core/src/main/java/org/photonvision/common/dataflow/websocket/UILightingConfig.java
+++ b/photon-core/src/main/java/org/photonvision/common/dataflow/websocket/UILightingConfig.java
@@ -17,6 +17,9 @@
package org.photonvision.common.dataflow.websocket;
+import io.avaje.jsonb.Json;
+
+@Json
public class UILightingConfig {
public UILightingConfig(int brightness, boolean supported) {
this.brightness = brightness;
diff --git a/photon-core/src/main/java/org/photonvision/common/dataflow/websocket/UINetConfig.java b/photon-core/src/main/java/org/photonvision/common/dataflow/websocket/UINetConfig.java
index dd146b7df..c41575474 100644
--- a/photon-core/src/main/java/org/photonvision/common/dataflow/websocket/UINetConfig.java
+++ b/photon-core/src/main/java/org/photonvision/common/dataflow/websocket/UINetConfig.java
@@ -17,11 +17,18 @@
package org.photonvision.common.dataflow.websocket;
+import io.avaje.jsonb.Json;
import java.util.List;
import org.photonvision.common.configuration.NetworkConfig;
import org.photonvision.common.networking.NetworkUtils.NMDeviceInfo;
+@Json
public class UINetConfig extends NetworkConfig {
+ // Constructor for JSON to allow all properties to be set directly
+ public UINetConfig() {
+ this.canManage = this.deviceCanManageNetwork();
+ }
+
public UINetConfig(
NetworkConfig config, List networkInterfaceNames, boolean networkingDisabled) {
super(config);
@@ -32,5 +39,7 @@ public class UINetConfig extends NetworkConfig {
public List networkInterfaceNames;
public boolean networkingDisabled;
+
+ @Json.Ignore(serialize = true)
public boolean canManage;
}
diff --git a/photon-core/src/main/java/org/photonvision/common/dataflow/websocket/UIPhotonConfiguration.java b/photon-core/src/main/java/org/photonvision/common/dataflow/websocket/UIPhotonConfiguration.java
index 6e5a14eb0..666ee57c2 100644
--- a/photon-core/src/main/java/org/photonvision/common/dataflow/websocket/UIPhotonConfiguration.java
+++ b/photon-core/src/main/java/org/photonvision/common/dataflow/websocket/UIPhotonConfiguration.java
@@ -17,6 +17,7 @@
package org.photonvision.common.dataflow.websocket;
+import io.avaje.jsonb.Json;
import java.util.List;
import org.photonvision.PhotonVersion;
import org.photonvision.common.LoadJNI;
@@ -31,6 +32,7 @@ import org.photonvision.common.networking.NetworkUtils;
import org.photonvision.vision.processes.VisionModule;
import org.photonvision.vision.processes.VisionSourceManager;
+@Json
public class UIPhotonConfiguration {
public List cameraSettings;
public UIProgramSettings settings;
@@ -59,7 +61,7 @@ public class UIPhotonConfiguration {
// TODO add support for other types of GPU accel
LoadJNI.hasLoaded(JNITypes.LIBCAMERA) ? "Zerocopy Libcamera Working" : "",
LoadJNI.hasLoaded(JNITypes.MRCAL),
- c.neuralNetworkPropertyManager().getModels(),
+ c.getNeuralNetworkProperties().getModels(),
NeuralNetworkModelManager.getInstance().getSupportedBackends(),
c.getHardwareConfig().deviceName.isEmpty()
? Platform.getHardwareModel()
diff --git a/photon-core/src/main/java/org/photonvision/common/dataflow/websocket/UIProgramSettings.java b/photon-core/src/main/java/org/photonvision/common/dataflow/websocket/UIProgramSettings.java
index 7b55ab0a0..c824c5861 100644
--- a/photon-core/src/main/java/org/photonvision/common/dataflow/websocket/UIProgramSettings.java
+++ b/photon-core/src/main/java/org/photonvision/common/dataflow/websocket/UIProgramSettings.java
@@ -17,8 +17,10 @@
package org.photonvision.common.dataflow.websocket;
+import io.avaje.jsonb.Json;
import org.wpilib.vision.apriltag.AprilTagFieldLayout;
+@Json
public class UIProgramSettings {
public UIProgramSettings(
UINetConfig networkSettings,
diff --git a/photon-core/src/main/java/org/photonvision/common/hardware/OsImageData.java b/photon-core/src/main/java/org/photonvision/common/hardware/OsImageData.java
index 9b2845ef6..d183431d5 100644
--- a/photon-core/src/main/java/org/photonvision/common/hardware/OsImageData.java
+++ b/photon-core/src/main/java/org/photonvision/common/hardware/OsImageData.java
@@ -17,10 +17,12 @@
package org.photonvision.common.hardware;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.PropertyNamingStrategies;
+import io.avaje.jsonb.Json;
+import io.avaje.jsonb.Jsonb;
+import java.io.File;
+import java.io.FileInputStream;
import java.io.IOException;
-import java.nio.file.Files;
+import java.io.InputStream;
import java.nio.file.Path;
import java.util.Optional;
import org.photonvision.common.logging.LogGroup;
@@ -35,26 +37,22 @@ import org.photonvision.common.logging.Logger;
public class OsImageData {
private static final Logger logger = new Logger(OsImageData.class, LogGroup.General);
- private static Path imageMetadataFile = Path.of("/opt/photonvision/image-version.json");
+ private static File imageMetadataFile = Path.of("/opt/photonvision/image-version.json").toFile();
public static final Optional IMAGE_METADATA = getImageMetadata();
+ @Json(naming = Json.Naming.LowerUnderscore)
public static record ImageMetadata(
String buildDate, String commitSha, String commitTag, String imageName, String imageSource) {}
private static Optional getImageMetadata() {
- if (!imageMetadataFile.toFile().exists()) {
+ if (!imageMetadataFile.exists()) {
logger.warn("Photon cannot locate OS image metadata at " + imageMetadataFile.toString());
return Optional.empty();
}
- try {
- String content = Files.readString(imageMetadataFile).strip();
-
- ObjectMapper mapper =
- new ObjectMapper().setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE);
-
- ImageMetadata md = mapper.readValue(content, ImageMetadata.class);
+ try (InputStream stream = new FileInputStream(imageMetadataFile)) {
+ ImageMetadata md = Jsonb.instance().type(ImageMetadata.class).fromJson(stream);
if (md.buildDate() == null
&& md.commitSha() == null
diff --git a/photon-core/src/main/java/org/photonvision/common/hardware/metrics/DeviceMetrics.java b/photon-core/src/main/java/org/photonvision/common/hardware/metrics/DeviceMetrics.java
index 095936077..e8789154c 100644
--- a/photon-core/src/main/java/org/photonvision/common/hardware/metrics/DeviceMetrics.java
+++ b/photon-core/src/main/java/org/photonvision/common/hardware/metrics/DeviceMetrics.java
@@ -17,8 +17,10 @@
package org.photonvision.common.hardware.metrics;
+import io.avaje.jsonb.Json;
import org.photonvision.common.hardware.metrics.proto.DeviceMetricsProto;
+@Json
public record DeviceMetrics(
double cpuTemp,
double cpuUtil,
diff --git a/photon-core/src/main/java/org/photonvision/common/networking/NetworkMode.java b/photon-core/src/main/java/org/photonvision/common/networking/NetworkMode.java
index c491e8662..aa1ff1b7f 100644
--- a/photon-core/src/main/java/org/photonvision/common/networking/NetworkMode.java
+++ b/photon-core/src/main/java/org/photonvision/common/networking/NetworkMode.java
@@ -17,13 +17,14 @@
package org.photonvision.common.networking;
-import com.fasterxml.jackson.annotation.JsonValue;
+import io.avaje.jsonb.Json;
+@Json
public enum NetworkMode {
DHCP,
STATIC;
- @JsonValue
+ @Json.Value
public int toValue() {
return ordinal();
}
diff --git a/photon-core/src/main/java/org/photonvision/common/networking/NetworkUtils.java b/photon-core/src/main/java/org/photonvision/common/networking/NetworkUtils.java
index df2525ef0..fde85a883 100644
--- a/photon-core/src/main/java/org/photonvision/common/networking/NetworkUtils.java
+++ b/photon-core/src/main/java/org/photonvision/common/networking/NetworkUtils.java
@@ -17,6 +17,7 @@
package org.photonvision.common.networking;
+import io.avaje.jsonb.Json;
import java.io.IOException;
import java.net.InetAddress;
import java.net.NetworkInterface;
@@ -62,9 +63,10 @@ public class NetworkUtils {
* @param devName The underlying device name, used by dhclient
* @param nmType The NetworkManager device type
*/
+ @Json
public static record NMDeviceInfo(String connName, String devName, NMType nmType) {
- public NMDeviceInfo(String c, String d, String type) {
- this(c, d, NMType.typeForString(type));
+ public NMDeviceInfo(String connName, String devName, String nmType) {
+ this(connName, devName, NMType.typeForString(nmType));
}
}
diff --git a/photon-core/src/main/java/org/photonvision/common/util/TestUtils.java b/photon-core/src/main/java/org/photonvision/common/util/TestUtils.java
index 430082a63..ffbe9d903 100644
--- a/photon-core/src/main/java/org/photonvision/common/util/TestUtils.java
+++ b/photon-core/src/main/java/org/photonvision/common/util/TestUtils.java
@@ -17,9 +17,11 @@
package org.photonvision.common.util;
-import com.fasterxml.jackson.databind.ObjectMapper;
+import io.avaje.json.JsonException;
+import io.avaje.jsonb.Jsonb;
import java.awt.HeadlessException;
import java.io.File;
+import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.Path;
import org.opencv.core.Mat;
@@ -342,12 +344,10 @@ public class TestUtils {
public static final String LIMELIGHT_480P_CAL_FILE = "limelight_1280_720.json";
public static CameraCalibrationCoefficients getCoeffs(String filename, boolean testMode) {
- try {
- return new ObjectMapper()
- .readValue(
- (Path.of(getCalibrationPath(testMode).toString(), filename).toFile()),
- CameraCalibrationCoefficients.class);
- } catch (IOException e) {
+ try (var stream =
+ new FileInputStream(Path.of(getCalibrationPath(testMode).toString(), filename).toFile())) {
+ return Jsonb.instance().type(CameraCalibrationCoefficients.class).fromJson(stream);
+ } catch (IOException | IllegalStateException | JsonException e) {
e.printStackTrace();
return null;
}
diff --git a/photon-core/src/main/java/org/photonvision/common/util/file/AprilTagFieldLayoutJsonAdapter.java b/photon-core/src/main/java/org/photonvision/common/util/file/AprilTagFieldLayoutJsonAdapter.java
new file mode 100644
index 000000000..67291a2a8
--- /dev/null
+++ b/photon-core/src/main/java/org/photonvision/common/util/file/AprilTagFieldLayoutJsonAdapter.java
@@ -0,0 +1,83 @@
+/*
+ * 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 .
+ */
+
+package org.photonvision.common.util.file;
+
+import io.avaje.json.JsonAdapter;
+import io.avaje.json.JsonReader;
+import io.avaje.json.JsonWriter;
+import io.avaje.json.PropertyNames;
+import io.avaje.jsonb.CustomAdapter;
+import io.avaje.jsonb.Json;
+import io.avaje.jsonb.Jsonb;
+import io.avaje.jsonb.Types;
+import java.util.List;
+import org.wpilib.vision.apriltag.AprilTag;
+import org.wpilib.vision.apriltag.AprilTagFieldLayout;
+
+@Json.Import(AprilTag.class)
+@CustomAdapter
+public class AprilTagFieldLayoutJsonAdapter implements JsonAdapter {
+ @Json
+ record FieldDimensions(double length, double width) {}
+
+ JsonAdapter> aprilTagListJsonAdapter;
+ JsonAdapter fieldDimensionsJsonAdapter;
+ PropertyNames names;
+
+ public AprilTagFieldLayoutJsonAdapter(Jsonb jsonb) {
+ aprilTagListJsonAdapter = jsonb.adapter(Types.listOf(AprilTag.class));
+ fieldDimensionsJsonAdapter = jsonb.adapter(FieldDimensions.class);
+ names = jsonb.properties("tags", "field");
+ }
+
+ @Override
+ public void toJson(JsonWriter writer, AprilTagFieldLayout value) {
+ writer.beginObject(names);
+ writer.name(0);
+ aprilTagListJsonAdapter.toJson(writer, value.getTags());
+ writer.name(1);
+ fieldDimensionsJsonAdapter.toJson(
+ writer, new FieldDimensions(value.getFieldLength(), value.getFieldWidth()));
+ writer.endObject();
+ }
+
+ @Override
+ public AprilTagFieldLayout fromJson(JsonReader reader) {
+ List tags = null;
+ FieldDimensions field = null;
+
+ reader.beginObject();
+ while (reader.hasNextField()) {
+ final String fieldName = reader.nextField();
+ switch (fieldName) {
+ case "tags":
+ tags = aprilTagListJsonAdapter.fromJson(reader);
+ break;
+ case "field":
+ field = fieldDimensionsJsonAdapter.fromJson(reader);
+ break;
+ default:
+ reader.unmappedField(fieldName);
+ reader.skipValue();
+ }
+ }
+ reader.endObject();
+
+ return new AprilTagFieldLayout(tags, field.length, field.width);
+ }
+}
diff --git a/photon-core/src/main/java/org/photonvision/common/util/file/JacksonUtils.java b/photon-core/src/main/java/org/photonvision/common/util/file/JacksonUtils.java
deleted file mode 100644
index d2edab277..000000000
--- a/photon-core/src/main/java/org/photonvision/common/util/file/JacksonUtils.java
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * 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 .
- */
-
-package org.photonvision.common.util.file;
-
-import com.fasterxml.jackson.core.JsonGenerator;
-import com.fasterxml.jackson.core.StreamReadFeature;
-import com.fasterxml.jackson.core.json.JsonReadFeature;
-import com.fasterxml.jackson.databind.DeserializationContext;
-import com.fasterxml.jackson.databind.DeserializationFeature;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.SerializerProvider;
-import com.fasterxml.jackson.databind.ext.NioPathDeserializer;
-import com.fasterxml.jackson.databind.ext.NioPathSerializer;
-import com.fasterxml.jackson.databind.json.JsonMapper;
-import com.fasterxml.jackson.databind.jsontype.BasicPolymorphicTypeValidator;
-import com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator;
-import com.fasterxml.jackson.databind.module.SimpleModule;
-import java.io.File;
-import java.io.FileDescriptor;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.net.URI;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.HashMap;
-import java.util.Map;
-import org.eclipse.jetty.io.EofException;
-
-public class JacksonUtils {
- public static class UIMap extends HashMap {}
-
- // Custom Path key deserializer for Maps with Path keys
- public static class PathKeySerializer
- extends com.fasterxml.jackson.databind.JsonSerializer {
- @Override
- public void serialize(Path value, JsonGenerator gen, SerializerProvider serializers)
- throws IOException {
- if (value == null) {
- gen.writeNull();
- } else {
- gen.writeFieldName(value.toUri().toString());
- }
- }
- }
-
- // Custom Path key deserializer for Maps with Path keys
- public static class PathKeyDeserializer extends com.fasterxml.jackson.databind.KeyDeserializer {
- @Override
- public Object deserializeKey(String key, DeserializationContext ctxt) throws IOException {
- if (key == null || key.isEmpty()) {
- return null;
- }
- return Paths.get(URI.create(key));
- }
- }
-
- // Helper method to create ObjectMapper with Path serialization support
- private static ObjectMapper createObjectMapperWithPathSupport(Class> baseType) {
- PolymorphicTypeValidator ptv =
- BasicPolymorphicTypeValidator.builder().allowIfBaseType(baseType).build();
-
- SimpleModule pathModule = new SimpleModule();
- pathModule.addSerializer(Path.class, new NioPathSerializer());
- pathModule.addKeySerializer(Path.class, new PathKeySerializer());
- pathModule.addDeserializer(Path.class, new NioPathDeserializer());
- pathModule.addKeyDeserializer(Path.class, new PathKeyDeserializer());
-
- return JsonMapper.builder()
- .enable(StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION)
- .configure(JsonReadFeature.ALLOW_JAVA_COMMENTS, true)
- .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
- .activateDefaultTyping(ptv, ObjectMapper.DefaultTyping.JAVA_LANG_OBJECT)
- .addModule(pathModule)
- .build();
- }
-
- public static void serialize(Path path, T object) throws IOException {
- serialize(path, object, true);
- }
-
- public static String serializeToString(T object) throws IOException {
- ObjectMapper objectMapper = createObjectMapperWithPathSupport(object.getClass());
- return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(object);
- }
-
- public static void serialize(Path path, T object, boolean forceSync) throws IOException {
- ObjectMapper objectMapper = createObjectMapperWithPathSupport(object.getClass());
- String json = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(object);
- saveJsonString(json, path, forceSync);
- }
-
- public static T deserialize(Map, ?> s, Class ref) throws IOException {
- ObjectMapper objectMapper = createObjectMapperWithPathSupport(ref);
- return objectMapper.convertValue(s, ref);
- }
-
- public static T deserialize(String s, Class ref) throws IOException {
- if (s.length() == 0) {
- throw new EofException("Provided empty string for class " + ref.getName());
- }
-
- ObjectMapper objectMapper = createObjectMapperWithPathSupport(ref);
- objectMapper.enable(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL);
-
- return objectMapper.readValue(s, ref);
- }
-
- public static T deserialize(Path path, Class ref) throws IOException {
- ObjectMapper objectMapper = createObjectMapperWithPathSupport(ref);
- File jsonFile = new File(path.toString());
- if (jsonFile.exists() && jsonFile.length() > 0) {
- return objectMapper.readValue(jsonFile, ref);
- }
- return null;
- }
-
- private static void saveJsonString(String json, Path path, boolean forceSync) throws IOException {
- var file = path.toFile();
- if (file.getParentFile() != null && !file.getParentFile().exists()) {
- file.getParentFile().mkdirs();
- }
- if (!file.exists()) {
- if (!file.canWrite()) {
- file.setWritable(true);
- }
- file.createNewFile();
- }
- FileOutputStream fileOutputStream = new FileOutputStream(file);
- fileOutputStream.write(json.getBytes());
- fileOutputStream.flush();
- if (forceSync) {
- FileDescriptor fileDescriptor = fileOutputStream.getFD();
- fileDescriptor.sync();
- }
- fileOutputStream.close();
- }
-}
diff --git a/photon-core/src/main/java/org/photonvision/common/util/file/PathAdapter.java b/photon-core/src/main/java/org/photonvision/common/util/file/PathAdapter.java
new file mode 100644
index 000000000..899c7c639
--- /dev/null
+++ b/photon-core/src/main/java/org/photonvision/common/util/file/PathAdapter.java
@@ -0,0 +1,41 @@
+/*
+ * 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 .
+ */
+
+package org.photonvision.common.util.file;
+
+import io.avaje.json.JsonAdapter;
+import io.avaje.json.JsonReader;
+import io.avaje.json.JsonWriter;
+import io.avaje.jsonb.CustomAdapter;
+import io.avaje.jsonb.Jsonb;
+import java.net.URI;
+import java.nio.file.Path;
+
+@CustomAdapter
+public class PathAdapter implements JsonAdapter {
+ public PathAdapter(Jsonb jsonb) {}
+
+ @Override
+ public Path fromJson(JsonReader reader) {
+ return Path.of(URI.create(reader.readString()));
+ }
+
+ @Override
+ public void toJson(JsonWriter writer, Path value) {
+ writer.value(value.toUri().toString());
+ }
+}
diff --git a/photon-core/src/main/java/org/photonvision/common/util/file/Pose3dJsonAdapter.java b/photon-core/src/main/java/org/photonvision/common/util/file/Pose3dJsonAdapter.java
new file mode 100644
index 000000000..60f1146db
--- /dev/null
+++ b/photon-core/src/main/java/org/photonvision/common/util/file/Pose3dJsonAdapter.java
@@ -0,0 +1,103 @@
+/*
+ * 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 .
+ */
+
+package org.photonvision.common.util.file;
+
+import io.avaje.json.JsonAdapter;
+import io.avaje.json.JsonReader;
+import io.avaje.json.JsonWriter;
+import io.avaje.json.PropertyNames;
+import io.avaje.json.view.ViewBuilder;
+import io.avaje.json.view.ViewBuilderAware;
+import io.avaje.jsonb.CustomAdapter;
+import io.avaje.jsonb.Jsonb;
+import java.lang.invoke.MethodHandle;
+import org.wpilib.math.geometry.Pose3d;
+import org.wpilib.math.geometry.Rotation3d;
+import org.wpilib.math.geometry.Translation3d;
+
+@CustomAdapter
+public class Pose3dJsonAdapter implements JsonAdapter, ViewBuilderAware {
+ private final JsonAdapter translation3dJsonAdapter;
+ private final JsonAdapter rotation3dJsonAdapter;
+ private final PropertyNames names;
+
+ public Pose3dJsonAdapter(Jsonb jsonb) {
+ this.translation3dJsonAdapter = jsonb.adapter(Translation3d.class);
+ this.rotation3dJsonAdapter = jsonb.adapter(Rotation3d.class);
+ this.names = jsonb.properties("translation", "rotation");
+ }
+
+ @Override
+ public void toJson(JsonWriter writer, Pose3d value) {
+ writer.beginObject(names);
+ writer.name(0);
+ translation3dJsonAdapter.toJson(writer, value.getTranslation());
+ writer.name(1);
+ rotation3dJsonAdapter.toJson(writer, value.getRotation());
+ writer.endObject();
+ }
+
+ @Override
+ public Pose3d fromJson(JsonReader reader) {
+ Translation3d translation = null;
+ Rotation3d rotation = null;
+
+ reader.beginObject(names);
+ while (reader.hasNextField()) {
+ final String fieldName = reader.nextField();
+ switch (fieldName) {
+ case "translation":
+ translation = translation3dJsonAdapter.fromJson(reader);
+ break;
+ case "rotation":
+ rotation = rotation3dJsonAdapter.fromJson(reader);
+ break;
+ default:
+ reader.unmappedField(fieldName);
+ reader.skipValue();
+ }
+ }
+ reader.endObject();
+
+ return new Pose3d(translation, rotation);
+ }
+
+ @Override
+ public boolean isViewBuilderAware() {
+ return true;
+ }
+
+ @Override
+ public ViewBuilderAware viewBuild() {
+ return this;
+ }
+
+ @Override
+ public void build(ViewBuilder builder, String name, MethodHandle handle) {
+ builder.beginObject(name, handle);
+ builder.add(
+ "translation",
+ translation3dJsonAdapter,
+ builder.method(Pose3d.class, "getTranslation", Translation3d.class));
+ builder.add(
+ "rotation",
+ rotation3dJsonAdapter,
+ builder.method(Pose3d.class, "getRotation", Rotation3d.class));
+ builder.endObject();
+ }
+}
diff --git a/photon-core/src/main/java/org/photonvision/common/util/file/QuaternionMixIn.java b/photon-core/src/main/java/org/photonvision/common/util/file/QuaternionMixIn.java
new file mode 100644
index 000000000..965df9452
--- /dev/null
+++ b/photon-core/src/main/java/org/photonvision/common/util/file/QuaternionMixIn.java
@@ -0,0 +1,45 @@
+/*
+ * 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 .
+ */
+
+package org.photonvision.common.util.file;
+
+import io.avaje.jsonb.Json;
+import org.wpilib.math.geometry.Quaternion;
+
+@Json.MixIn(Quaternion.class)
+public class QuaternionMixIn {
+ @Json.Ignore(deserialize = true)
+ @Json.Property("W")
+ double m_w;
+
+ @Json.Ignore(deserialize = true)
+ @Json.Property("X")
+ double m_x;
+
+ @Json.Ignore(deserialize = true)
+ @Json.Property("Y")
+ double m_y;
+
+ @Json.Ignore(deserialize = true)
+ @Json.Property("Z")
+ double m_z;
+
+ @Json.Creator
+ public static Quaternion construct(double m_w, double m_x, double m_y, double m_z) {
+ return new Quaternion(m_w, m_x, m_y, m_z);
+ }
+}
diff --git a/photon-core/src/main/java/org/photonvision/common/util/file/Rotation3dMixIn.java b/photon-core/src/main/java/org/photonvision/common/util/file/Rotation3dMixIn.java
new file mode 100644
index 000000000..f89b594f3
--- /dev/null
+++ b/photon-core/src/main/java/org/photonvision/common/util/file/Rotation3dMixIn.java
@@ -0,0 +1,34 @@
+/*
+ * 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 .
+ */
+
+package org.photonvision.common.util.file;
+
+import io.avaje.jsonb.Json;
+import org.wpilib.math.geometry.Quaternion;
+import org.wpilib.math.geometry.Rotation3d;
+
+@Json.MixIn(Rotation3d.class)
+public class Rotation3dMixIn {
+ @Json.Ignore(deserialize = true)
+ @Json.Property("quaternion")
+ Quaternion m_q;
+
+ @Json.Creator
+ public static Rotation3d construct(Quaternion m_q) {
+ return new Rotation3d(m_q);
+ }
+}
diff --git a/photon-core/src/main/java/org/photonvision/common/util/file/Translation3dMixIn.java b/photon-core/src/main/java/org/photonvision/common/util/file/Translation3dMixIn.java
new file mode 100644
index 000000000..80e3e77bf
--- /dev/null
+++ b/photon-core/src/main/java/org/photonvision/common/util/file/Translation3dMixIn.java
@@ -0,0 +1,41 @@
+/*
+ * 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 .
+ */
+
+package org.photonvision.common.util.file;
+
+import io.avaje.jsonb.Json;
+import org.wpilib.math.geometry.Translation3d;
+
+@Json.MixIn(Translation3d.class)
+public class Translation3dMixIn {
+ @Json.Ignore(deserialize = true)
+ @Json.Property("x")
+ double m_x;
+
+ @Json.Ignore(deserialize = true)
+ @Json.Property("y")
+ double m_y;
+
+ @Json.Ignore(deserialize = true)
+ @Json.Property("z")
+ double m_z;
+
+ @Json.Creator
+ public static Translation3d construct(double m_x, double m_y, double m_z) {
+ return new Translation3d(m_x, m_y, m_z);
+ }
+}
diff --git a/photon-core/src/main/java/org/photonvision/common/util/numbers/DoubleCouple.java b/photon-core/src/main/java/org/photonvision/common/util/numbers/DoubleCouple.java
index d8503503e..e1a40a05c 100644
--- a/photon-core/src/main/java/org/photonvision/common/util/numbers/DoubleCouple.java
+++ b/photon-core/src/main/java/org/photonvision/common/util/numbers/DoubleCouple.java
@@ -17,8 +17,10 @@
package org.photonvision.common.util.numbers;
+import io.avaje.jsonb.Json;
import org.opencv.core.Point;
+@Json
public class DoubleCouple extends NumberCouple {
public DoubleCouple() {
super(0.0, 0.0);
diff --git a/photon-core/src/main/java/org/photonvision/common/util/numbers/IntegerCouple.java b/photon-core/src/main/java/org/photonvision/common/util/numbers/IntegerCouple.java
index 0cd834cc2..a7010bbaf 100644
--- a/photon-core/src/main/java/org/photonvision/common/util/numbers/IntegerCouple.java
+++ b/photon-core/src/main/java/org/photonvision/common/util/numbers/IntegerCouple.java
@@ -17,6 +17,9 @@
package org.photonvision.common.util.numbers;
+import io.avaje.jsonb.Json;
+
+@Json
public class IntegerCouple extends NumberCouple {
public IntegerCouple() {
super(0, 0);
diff --git a/photon-core/src/main/java/org/photonvision/common/util/numbers/NumberCouple.java b/photon-core/src/main/java/org/photonvision/common/util/numbers/NumberCouple.java
index b6b43323a..ebb4306d7 100644
--- a/photon-core/src/main/java/org/photonvision/common/util/numbers/NumberCouple.java
+++ b/photon-core/src/main/java/org/photonvision/common/util/numbers/NumberCouple.java
@@ -17,8 +17,6 @@
package org.photonvision.common.util.numbers;
-import com.fasterxml.jackson.annotation.JsonIgnore;
-
public abstract class NumberCouple {
protected T first;
protected T second;
@@ -56,7 +54,6 @@ public abstract class NumberCouple {
&& couple.second.equals(second);
}
- @JsonIgnore
public boolean isEmpty() {
return first.intValue() == 0 && second.intValue() == 0;
}
diff --git a/photon-core/src/main/java/org/photonvision/vision/calibration/BoardObservation.java b/photon-core/src/main/java/org/photonvision/vision/calibration/BoardObservation.java
index 6ce582a00..6e4d91f40 100644
--- a/photon-core/src/main/java/org/photonvision/vision/calibration/BoardObservation.java
+++ b/photon-core/src/main/java/org/photonvision/vision/calibration/BoardObservation.java
@@ -17,10 +17,7 @@
package org.photonvision.vision.calibration;
-import com.fasterxml.jackson.annotation.JsonCreator;
-import com.fasterxml.jackson.annotation.JsonIgnore;
-import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
-import com.fasterxml.jackson.annotation.JsonProperty;
+import io.avaje.jsonb.Json;
import java.awt.Color;
import java.nio.file.Path;
import java.util.Arrays;
@@ -36,45 +33,36 @@ import org.opencv.imgproc.Imgproc;
import org.photonvision.common.util.ColorHelper;
import org.wpilib.math.geometry.Pose3d;
+@Json
// Ignore the previous calibration data that was stored in the json file.
-@JsonIgnoreProperties(ignoreUnknown = true)
public final class BoardObservation implements Cloneable {
// Expected feature 3d location in the camera frame
- @JsonProperty("locationInObjectSpace")
public List locationInObjectSpace;
// Observed location in pixel space
- @JsonProperty("locationInImageSpace")
public List locationInImageSpace;
// (measured location in pixels) - (expected from FK)
- @JsonProperty("reprojectionErrors")
public List reprojectionErrors;
// Solver optimized board poses
- @JsonProperty("optimisedCameraToObject")
public Pose3d optimisedCameraToObject;
// If we should use this observation when re-calculating camera calibration
- @JsonProperty("cornersUsed")
public boolean[] cornersUsed;
- @JsonProperty("snapshotName")
public String snapshotName;
- @JsonProperty("snapshotDataLocation")
- @Nullable
- public Path snapshotDataLocation;
+ @Nullable public Path snapshotDataLocation;
- @JsonCreator
public BoardObservation(
- @JsonProperty("locationInObjectSpace") List locationInObjectSpace,
- @JsonProperty("locationInImageSpace") List locationInImageSpace,
- @JsonProperty("reprojectionErrors") List reprojectionErrors,
- @JsonProperty("optimisedCameraToObject") Pose3d optimisedCameraToObject,
- @JsonProperty("cornersUsed") boolean[] cornersUsed,
- @JsonProperty("snapshotName") String snapshotName,
- @JsonProperty("snapshotDataLocation") Path snapshotDataLocation) {
+ List locationInObjectSpace,
+ List locationInImageSpace,
+ List reprojectionErrors,
+ Pose3d optimisedCameraToObject,
+ boolean[] cornersUsed,
+ String snapshotName,
+ Path snapshotDataLocation) {
this.locationInObjectSpace = locationInObjectSpace;
this.locationInImageSpace = locationInImageSpace;
this.reprojectionErrors = reprojectionErrors;
@@ -119,7 +107,6 @@ public final class BoardObservation implements Cloneable {
}
}
- @JsonIgnore
/**
* Load the captured board image from disk. Allocates a new Mat, which the caller is responsible
* for releasing.
@@ -141,7 +128,6 @@ public final class BoardObservation implements Cloneable {
* @return Annotated image, or null if the image could not be loaded. Caller is responsible for
* releasing the Mat.
*/
- @JsonIgnore
public Mat annotateImage() {
var image = loadImage();
@@ -179,7 +165,6 @@ public final class BoardObservation implements Cloneable {
*
* @return Mean reprojection error in pixels.
*/
- @JsonIgnore
double meanReprojectionError() {
return reprojectionErrors.stream()
.filter(pt -> cornersUsed[reprojectionErrors.indexOf(pt)])
diff --git a/photon-core/src/main/java/org/photonvision/vision/calibration/CameraCalibrationCoefficients.java b/photon-core/src/main/java/org/photonvision/vision/calibration/CameraCalibrationCoefficients.java
index ad0f9db98..3999437ba 100644
--- a/photon-core/src/main/java/org/photonvision/vision/calibration/CameraCalibrationCoefficients.java
+++ b/photon-core/src/main/java/org/photonvision/vision/calibration/CameraCalibrationCoefficients.java
@@ -17,11 +17,7 @@
package org.photonvision.vision.calibration;
-import com.fasterxml.jackson.annotation.JsonAlias;
-import com.fasterxml.jackson.annotation.JsonCreator;
-import com.fasterxml.jackson.annotation.JsonIgnore;
-import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
-import com.fasterxml.jackson.annotation.JsonProperty;
+import io.avaje.jsonb.Json;
import java.util.Arrays;
import java.util.List;
import org.opencv.core.Mat;
@@ -30,31 +26,23 @@ import org.opencv.core.Size;
import org.photonvision.vision.opencv.ImageRotationMode;
import org.photonvision.vision.opencv.Releasable;
-@JsonIgnoreProperties(ignoreUnknown = true)
+@Json
public class CameraCalibrationCoefficients implements Releasable {
- @JsonProperty("resolution")
- public final Size unrotatedImageSize;
+ /** The unrotated resolution of the calibration */
+ public final Size resolution;
- @JsonProperty("cameraIntrinsics")
public final JsonMatOfDouble cameraIntrinsics;
- @JsonProperty("distCoeffs")
- @JsonAlias({"distCoeffs", "distCoeffs"})
public final JsonMatOfDouble distCoeffs;
- @JsonProperty("observations")
public final List observations;
- @JsonProperty("calobjectWarp")
public final double[] calobjectWarp;
- @JsonProperty("calobjectSize")
public final Size calobjectSize;
- @JsonProperty("calobjectSpacing")
public final double calobjectSpacing;
- @JsonProperty("lensmodel")
public final CameraLensModel lensmodel;
/**
@@ -75,17 +63,16 @@ public class CameraCalibrationCoefficients implements Releasable {
* width/height
* @param calobjectSpacing Spacing between adjacent squares, in meters
*/
- @JsonCreator
public CameraCalibrationCoefficients(
- @JsonProperty("resolution") Size resolution,
- @JsonProperty("cameraIntrinsics") JsonMatOfDouble cameraIntrinsics,
- @JsonProperty("distCoeffs") JsonMatOfDouble distCoeffs,
- @JsonProperty("calobjectWarp") double[] calobjectWarp,
- @JsonProperty("observations") List observations,
- @JsonProperty("calobjectSize") Size calobjectSize,
- @JsonProperty("calobjectSpacing") double calobjectSpacing,
- @JsonProperty("lensmodel") CameraLensModel lensmodel) {
- this.unrotatedImageSize = resolution;
+ Size resolution,
+ JsonMatOfDouble cameraIntrinsics,
+ JsonMatOfDouble distCoeffs,
+ double[] calobjectWarp,
+ List observations,
+ Size calobjectSize,
+ double calobjectSpacing,
+ CameraLensModel lensmodel) {
+ this.resolution = resolution;
this.cameraIntrinsics = cameraIntrinsics;
this.distCoeffs = distCoeffs;
this.calobjectWarp = calobjectWarp;
@@ -129,7 +116,7 @@ public class CameraCalibrationCoefficients implements Releasable {
rotatedIntrinsics.put(1, 1, fx);
// CX
- rotatedIntrinsics.put(0, 2, unrotatedImageSize.height - cy);
+ rotatedIntrinsics.put(0, 2, resolution.height - cy);
// CY
rotatedIntrinsics.put(1, 2, cx);
@@ -139,14 +126,14 @@ public class CameraCalibrationCoefficients implements Releasable {
rotatedDistCoeffs.put(0, 3, -p1);
// The rotated image size is the same as the unrotated image size, but the width and height
- // are flipped
- rotatedImageSize = new Size(unrotatedImageSize.height, unrotatedImageSize.width);
+ // are swapped
+ rotatedImageSize = new Size(resolution.height, resolution.width);
break;
case DEG_180_CCW:
// CX
- rotatedIntrinsics.put(0, 2, unrotatedImageSize.width - cx);
+ rotatedIntrinsics.put(0, 2, resolution.width - cx);
// CY
- rotatedIntrinsics.put(1, 2, unrotatedImageSize.height - cy);
+ rotatedIntrinsics.put(1, 2, resolution.height - cy);
// P1
rotatedDistCoeffs.put(0, 2, -p1);
@@ -154,7 +141,7 @@ public class CameraCalibrationCoefficients implements Releasable {
rotatedDistCoeffs.put(0, 3, -p2);
// The rotated image size is the same as the unrotated image size
- rotatedImageSize = unrotatedImageSize;
+ rotatedImageSize = resolution;
break;
case DEG_90_CCW:
// FX
@@ -165,7 +152,7 @@ public class CameraCalibrationCoefficients implements Releasable {
// CX
rotatedIntrinsics.put(0, 2, cy);
// CY
- rotatedIntrinsics.put(1, 2, unrotatedImageSize.width - cx);
+ rotatedIntrinsics.put(1, 2, resolution.width - cx);
// P1
rotatedDistCoeffs.put(0, 2, -p2);
@@ -173,8 +160,8 @@ public class CameraCalibrationCoefficients implements Releasable {
rotatedDistCoeffs.put(0, 3, p1);
// The rotated image size is the same as the unrotated image size, but the width and height
- // are flipped
- rotatedImageSize = new Size(unrotatedImageSize.height, unrotatedImageSize.width);
+ // are swapped
+ rotatedImageSize = new Size(resolution.height, resolution.width);
break;
}
@@ -196,27 +183,22 @@ public class CameraCalibrationCoefficients implements Releasable {
lensmodel);
}
- @JsonIgnore
public Mat getCameraIntrinsicsMat() {
return cameraIntrinsics.getAsMatOfDouble();
}
- @JsonIgnore
public MatOfDouble getDistCoeffsMat() {
return distCoeffs.getAsMatOfDouble();
}
- @JsonIgnore
public double[] getIntrinsicsArr() {
return cameraIntrinsics.data;
}
- @JsonIgnore
public double[] getDistCoeffsArr() {
return distCoeffs.data;
}
- @JsonIgnore
public List getObservations() {
return observations;
}
@@ -230,7 +212,7 @@ public class CameraCalibrationCoefficients implements Releasable {
@Override
public String toString() {
return "CameraCalibrationCoefficients [resolution="
- + unrotatedImageSize
+ + resolution
+ ", cameraIntrinsics="
+ cameraIntrinsics
+ ", distCoeffs="
@@ -244,7 +226,7 @@ public class CameraCalibrationCoefficients implements Releasable {
public UICameraCalibrationCoefficients cloneWithoutObservations() {
return new UICameraCalibrationCoefficients(
- unrotatedImageSize,
+ resolution,
cameraIntrinsics,
distCoeffs,
calobjectWarp,
diff --git a/photon-core/src/main/java/org/photonvision/vision/calibration/JsonMatOfDouble.java b/photon-core/src/main/java/org/photonvision/vision/calibration/JsonMatOfDouble.java
index f2f640402..eeb5aa58f 100644
--- a/photon-core/src/main/java/org/photonvision/vision/calibration/JsonMatOfDouble.java
+++ b/photon-core/src/main/java/org/photonvision/vision/calibration/JsonMatOfDouble.java
@@ -17,8 +17,7 @@
package org.photonvision.vision.calibration;
-import com.fasterxml.jackson.annotation.JsonIgnore;
-import com.fasterxml.jackson.annotation.JsonProperty;
+import io.avaje.jsonb.Json;
import java.util.Arrays;
import org.ejml.simple.SimpleMatrix;
import org.opencv.core.CvType;
@@ -28,6 +27,7 @@ import org.photonvision.vision.opencv.Releasable;
import org.wpilib.math.linalg.Matrix;
import org.wpilib.math.util.Num;
+@Json
/** JSON-serializable image. Data is stored as a raw JSON array. */
public class JsonMatOfDouble implements Releasable {
public final int rows;
@@ -36,28 +36,23 @@ public class JsonMatOfDouble implements Releasable {
public final double[] data;
// Cached matrices to avoid object recreation
- @JsonIgnore private Mat wrappedMat = null;
- @JsonIgnore private Matrix wpilibMat = null;
+ @Json.Ignore private Mat wrappedMat = null;
+ @Json.Ignore private Matrix wpilibMat = null;
- @JsonIgnore private MatOfDouble wrappedMatOfDouble;
- private boolean released = false;
+ @Json.Ignore private MatOfDouble wrappedMatOfDouble;
+ @Json.Ignore private boolean released = false;
public JsonMatOfDouble(int rows, int cols, double[] data) {
this(rows, cols, CvType.CV_64FC1, data);
}
- public JsonMatOfDouble(
- @JsonProperty("rows") int rows,
- @JsonProperty("cols") int cols,
- @JsonProperty("type") int type,
- @JsonProperty("data") double[] data) {
+ public JsonMatOfDouble(int rows, int cols, int type, double[] data) {
this.rows = rows;
this.cols = cols;
this.type = type;
this.data = data;
}
- @JsonIgnore
private static double[] getDataFromMat(Mat mat) {
double[] data = new double[(int) mat.total()];
mat.get(0, 0, data);
@@ -75,7 +70,6 @@ public class JsonMatOfDouble implements Releasable {
return new JsonMatOfDouble(mat.rows(), mat.cols(), getDataFromMat(mat));
}
- @JsonIgnore
private Mat getAsMat() {
if (this.type != CvType.CV_64FC1) return null;
@@ -91,7 +85,6 @@ public class JsonMatOfDouble implements Releasable {
return this.wrappedMat;
}
- @JsonIgnore
public MatOfDouble getAsMatOfDouble() {
if (this.released) {
throw new RuntimeException("This calibration object was already released");
@@ -105,7 +98,6 @@ public class JsonMatOfDouble implements Releasable {
}
@SuppressWarnings("unchecked")
- @JsonIgnore
public Matrix getAsWpilibMat() {
if (wpilibMat == null) {
wpilibMat = new Matrix(new SimpleMatrix(rows, cols, true, data));
diff --git a/photon-core/src/main/java/org/photonvision/vision/calibration/UICameraCalibrationCoefficients.java b/photon-core/src/main/java/org/photonvision/vision/calibration/UICameraCalibrationCoefficients.java
index 823f9fec0..96a1eb7d6 100644
--- a/photon-core/src/main/java/org/photonvision/vision/calibration/UICameraCalibrationCoefficients.java
+++ b/photon-core/src/main/java/org/photonvision/vision/calibration/UICameraCalibrationCoefficients.java
@@ -17,10 +17,12 @@
package org.photonvision.vision.calibration;
+import io.avaje.jsonb.Json;
import java.util.List;
import java.util.stream.IntStream;
import org.opencv.core.Size;
+@Json
public class UICameraCalibrationCoefficients extends CameraCalibrationCoefficients {
public int numSnapshots;
diff --git a/photon-core/src/main/java/org/photonvision/vision/camera/FileVisionSource.java b/photon-core/src/main/java/org/photonvision/vision/camera/FileVisionSource.java
index 59e15ca59..8a2937cc7 100644
--- a/photon-core/src/main/java/org/photonvision/vision/camera/FileVisionSource.java
+++ b/photon-core/src/main/java/org/photonvision/vision/camera/FileVisionSource.java
@@ -18,7 +18,8 @@
package org.photonvision.vision.camera;
import java.nio.file.Path;
-import java.util.HashMap;
+import java.util.ArrayList;
+import java.util.List;
import org.photonvision.common.configuration.CameraConfiguration;
import org.photonvision.vision.frame.FrameProvider;
import org.photonvision.vision.frame.FrameStaticProperties;
@@ -100,7 +101,7 @@ public class FileVisionSource extends VisionSource {
public static class FileSourceSettables extends VisionSourceSettables {
private final VideoMode videoMode;
- private final HashMap videoModes = new HashMap<>();
+ private final ArrayList videoModes = new ArrayList<>();
FileSourceSettables(
CameraConfiguration cameraConfiguration, FrameStaticProperties frameStaticProperties) {
@@ -112,7 +113,7 @@ public class FileVisionSource extends VisionSource {
frameStaticProperties.imageWidth,
frameStaticProperties.imageHeight,
30);
- videoModes.put(0, videoMode);
+ videoModes.add(videoMode);
}
@Override
@@ -137,7 +138,7 @@ public class FileVisionSource extends VisionSource {
}
@Override
- public HashMap getAllVideoModes() {
+ public List getAllVideoModes() {
return videoModes;
}
diff --git a/photon-core/src/main/java/org/photonvision/vision/camera/PVCameraInfo.java b/photon-core/src/main/java/org/photonvision/vision/camera/PVCameraInfo.java
index e861bab14..dd87db78d 100644
--- a/photon-core/src/main/java/org/photonvision/vision/camera/PVCameraInfo.java
+++ b/photon-core/src/main/java/org/photonvision/vision/camera/PVCameraInfo.java
@@ -17,33 +17,31 @@
package org.photonvision.vision.camera;
-import com.fasterxml.jackson.annotation.JsonCreator;
-import com.fasterxml.jackson.annotation.JsonGetter;
-import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
-import com.fasterxml.jackson.annotation.JsonProperty;
-import com.fasterxml.jackson.annotation.JsonSubTypes;
-import com.fasterxml.jackson.annotation.JsonTypeInfo;
-import com.fasterxml.jackson.annotation.JsonTypeName;
+import io.avaje.jsonb.Json;
+import io.avaje.jsonb.JsonType;
+import io.avaje.jsonb.Jsonb;
+import io.avaje.jsonb.Types;
import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
import java.util.Objects;
import org.wpilib.vision.camera.UsbCameraInfo;
-@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.WRAPPER_OBJECT)
-@JsonIgnoreProperties(ignoreUnknown = true)
-@JsonSubTypes({
- @JsonSubTypes.Type(value = PVCameraInfo.PVUsbCameraInfo.class),
- @JsonSubTypes.Type(value = PVCameraInfo.PVCSICameraInfo.class),
- @JsonSubTypes.Type(value = PVCameraInfo.PVFileCameraInfo.class)
-})
+@Json(typeProperty = "type")
+@Json.SubType(type = PVCameraInfo.PVUsbCameraInfo.class)
+@Json.SubType(type = PVCameraInfo.PVCSICameraInfo.class)
+@Json.SubType(type = PVCameraInfo.PVFileCameraInfo.class)
public sealed interface PVCameraInfo {
/**
* @return The path of the camera.
*/
+ @Json.Property("path")
String path();
/**
* @return The base name of the camera aka the name as just ascii.
*/
+ @Json.Property("name")
String name();
/**
@@ -64,7 +62,7 @@ public sealed interface PVCameraInfo {
*
* @return The unique path of the camera
*/
- @JsonGetter(value = "uniquePath")
+ @Json.Property("uniquePath")
String uniquePath();
String[] otherPaths();
@@ -82,16 +80,9 @@ public sealed interface PVCameraInfo {
return this.equals((Object) other);
}
- @JsonTypeName("PVUsbCameraInfo")
public static final class PVUsbCameraInfo extends UsbCameraInfo implements PVCameraInfo {
- @JsonCreator
public PVUsbCameraInfo(
- @JsonProperty("dev") int dev,
- @JsonProperty("path") String path,
- @JsonProperty("name") String name,
- @JsonProperty("otherPaths") String[] otherPaths,
- @JsonProperty("vendorId") int vendorId,
- @JsonProperty("productId") int productId) {
+ int dev, String path, String name, String[] otherPaths, int vendorId, int productId) {
super(dev, path, name, otherPaths, vendorId, productId);
}
@@ -168,14 +159,11 @@ public sealed interface PVCameraInfo {
}
}
- @JsonTypeName("PVCSICameraInfo")
public static final class PVCSICameraInfo implements PVCameraInfo {
public final String path;
public final String baseName;
- @JsonCreator
- public PVCSICameraInfo(
- @JsonProperty("path") String path, @JsonProperty("baseName") String baseName) {
+ public PVCSICameraInfo(String path, String baseName) {
this.path = path;
this.baseName = baseName;
}
@@ -233,13 +221,11 @@ public sealed interface PVCameraInfo {
}
}
- @JsonTypeName("PVFileCameraInfo")
public static final class PVFileCameraInfo implements PVCameraInfo {
public final String path;
public final String name;
- @JsonCreator
- public PVFileCameraInfo(@JsonProperty("path") String path, @JsonProperty("name") String name) {
+ public PVFileCameraInfo(String path, String name) {
this.path = path;
this.name = name;
}
@@ -300,4 +286,25 @@ public sealed interface PVCameraInfo {
public static PVCameraInfo fromFileInfo(String path, String baseName) {
return new PVFileCameraInfo(path, baseName);
}
+
+ // MIGRATION: 2026
+ public static String remapConfigJson(String configJson, String cameraType) {
+ final JsonType> objMapJsonb =
+ Jsonb.instance().type(Types.mapOf(Object.class));
+
+ Map cameraMigration = objMapJsonb.fromJson(configJson);
+
+ @SuppressWarnings("unchecked")
+ var cameraMigrationIn = (Map) cameraMigration.get("matchedCameraInfo");
+
+ @SuppressWarnings("unchecked")
+ var cameraData = (Map) cameraMigrationIn.get(cameraType);
+
+ Map cameraMigrationOut = new HashMap<>();
+ cameraMigrationOut.putAll(cameraData);
+ cameraMigrationOut.put("type", "PVCameraInfo." + cameraType);
+
+ cameraMigration.put("matchedCameraInfo", cameraMigrationOut);
+ return objMapJsonb.toJson(cameraMigration);
+ }
}
diff --git a/photon-core/src/main/java/org/photonvision/vision/camera/QuirkyCamera.java b/photon-core/src/main/java/org/photonvision/vision/camera/QuirkyCamera.java
index c50059222..c6556e589 100644
--- a/photon-core/src/main/java/org/photonvision/vision/camera/QuirkyCamera.java
+++ b/photon-core/src/main/java/org/photonvision/vision/camera/QuirkyCamera.java
@@ -17,13 +17,13 @@
package org.photonvision.vision.camera;
-import com.fasterxml.jackson.annotation.JsonCreator;
-import com.fasterxml.jackson.annotation.JsonProperty;
+import io.avaje.jsonb.Json;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+@Json
public class QuirkyCamera {
private static final List quirkyCameras =
List.of(
@@ -98,19 +98,14 @@ public class QuirkyCamera {
CameraQuirk.Gain,
CameraQuirk.AwbRedBlueGain); // PiCam (using libcamera GPU Driver on raspberry pi)
- @JsonProperty("baseName")
public final String baseName;
- @JsonProperty("usbVid")
public final int usbVid;
- @JsonProperty("usbPid")
public final int usbPid;
- @JsonProperty("displayName")
public final String displayName;
- @JsonProperty("quirks")
public final Map quirks;
/**
@@ -165,13 +160,12 @@ public class QuirkyCamera {
}
}
- @JsonCreator
public QuirkyCamera(
- @JsonProperty("baseName") String baseName,
- @JsonProperty("usbVid") int usbVid,
- @JsonProperty("usbPid") int usbPid,
- @JsonProperty("displayName") String displayName,
- @JsonProperty("quirks") Map quirks) {
+ String baseName,
+ int usbVid,
+ int usbPid,
+ String displayName,
+ Map quirks) {
this.baseName = baseName;
this.usbPid = usbPid;
this.usbVid = usbVid;
diff --git a/photon-core/src/main/java/org/photonvision/vision/camera/USBCameras/GenericUSBCameraSettables.java b/photon-core/src/main/java/org/photonvision/vision/camera/USBCameras/GenericUSBCameraSettables.java
index 24c97a41d..9992d7641 100644
--- a/photon-core/src/main/java/org/photonvision/vision/camera/USBCameras/GenericUSBCameraSettables.java
+++ b/photon-core/src/main/java/org/photonvision/vision/camera/USBCameras/GenericUSBCameraSettables.java
@@ -20,7 +20,6 @@ package org.photonvision.vision.camera.USBCameras;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
-import java.util.HashMap;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
@@ -286,7 +285,6 @@ public class GenericUSBCameraSettables extends VisionSourceSettables {
}
private void cacheVideoModes() {
- videoModes = new HashMap<>();
List videoModesList = new ArrayList<>();
try {
for (VideoMode videoMode : camera.enumerateVideoModes()) {
@@ -316,10 +314,7 @@ public class GenericUSBCameraSettables extends VisionSourceSettables {
// The ordering is usually more logical when done like this. It typically puts higher FPSes
// closer to the bottom.
Collections.reverse(sortedList);
-
- for (int i = 0; i < sortedList.size(); i++) {
- videoModes.put(i, sortedList.get(i));
- }
+ videoModes = sortedList;
// If after all that we still have no video modes, not much we can do besides
// throw up our hands
@@ -329,11 +324,11 @@ public class GenericUSBCameraSettables extends VisionSourceSettables {
}
@Override
- public HashMap getAllVideoModes() {
+ public List getAllVideoModes() {
if (!cameraPropertiesCached) {
// Device hasn't connected at least once, best I can do is given up
logger.warn("Device hasn't connected, cannot enumerate video modes");
- return new HashMap<>();
+ return new ArrayList<>();
}
return videoModes;
diff --git a/photon-core/src/main/java/org/photonvision/vision/camera/csi/LibcameraGpuSettables.java b/photon-core/src/main/java/org/photonvision/vision/camera/csi/LibcameraGpuSettables.java
index 5994fdbb8..c9d3666a0 100644
--- a/photon-core/src/main/java/org/photonvision/vision/camera/csi/LibcameraGpuSettables.java
+++ b/photon-core/src/main/java/org/photonvision/vision/camera/csi/LibcameraGpuSettables.java
@@ -17,7 +17,8 @@
package org.photonvision.vision.camera.csi;
-import java.util.HashMap;
+import java.util.ArrayList;
+import java.util.List;
import org.photonvision.common.configuration.CameraConfiguration;
import org.photonvision.common.util.math.MathUtils;
import org.photonvision.raspi.LibCameraJNI;
@@ -61,7 +62,7 @@ public class LibcameraGpuSettables extends VisionSourceSettables {
public LibcameraGpuSettables(CameraConfiguration configuration) {
super(configuration);
- videoModes = new HashMap<>();
+ videoModes = new ArrayList<>();
LibCameraJNI.SensorModel tempSensorModel;
try {
@@ -74,18 +75,18 @@ public class LibcameraGpuSettables extends VisionSourceSettables {
if (sensorModel == LibCameraJNI.SensorModel.IMX219) {
// Settings for the IMX219 sensor, which is used on the Pi Camera Module v2
- videoModes.put(0, new FPSRatedVideoMode(PixelFormat.kUnknown, 320, 240, 120, 120, .39));
- videoModes.put(1, new FPSRatedVideoMode(PixelFormat.kUnknown, 320, 240, 30, 30, .39));
- videoModes.put(2, new FPSRatedVideoMode(PixelFormat.kUnknown, 640, 480, 65, 90, .39));
- videoModes.put(3, new FPSRatedVideoMode(PixelFormat.kUnknown, 640, 480, 30, 30, .39));
+ videoModes.add(new FPSRatedVideoMode(PixelFormat.kUnknown, 320, 240, 120, 120, .39));
+ videoModes.add(new FPSRatedVideoMode(PixelFormat.kUnknown, 320, 240, 30, 30, .39));
+ videoModes.add(new FPSRatedVideoMode(PixelFormat.kUnknown, 640, 480, 65, 90, .39));
+ videoModes.add(new FPSRatedVideoMode(PixelFormat.kUnknown, 640, 480, 30, 30, .39));
// TODO: fix 1280x720 in the native code and re-add it
- videoModes.put(4, new FPSRatedVideoMode(PixelFormat.kUnknown, 1920, 1080, 15, 20, .53));
- videoModes.put(5, new FPSRatedVideoMode(PixelFormat.kUnknown, 3280 / 2, 2464 / 2, 15, 20, 1));
- videoModes.put(6, new FPSRatedVideoMode(PixelFormat.kUnknown, 3280 / 4, 2464 / 4, 15, 20, 1));
+ videoModes.add(new FPSRatedVideoMode(PixelFormat.kUnknown, 1920, 1080, 15, 20, .53));
+ videoModes.add(new FPSRatedVideoMode(PixelFormat.kUnknown, 3280 / 2, 2464 / 2, 15, 20, 1));
+ videoModes.add(new FPSRatedVideoMode(PixelFormat.kUnknown, 3280 / 4, 2464 / 4, 15, 20, 1));
} else if (sensorModel == LibCameraJNI.SensorModel.OV9281) {
// Taken from https://www.ovt.com/wp-content/uploads/2022/01/OV9281-OV9282-PB-v1.3-WEB.pdf
- videoModes.put(0, new FPSRatedVideoMode(PixelFormat.kUnknown, 640, 400, 120, 240, 1));
- videoModes.put(1, new FPSRatedVideoMode(PixelFormat.kUnknown, 1280, 800, 120, 120, 1));
+ videoModes.add(new FPSRatedVideoMode(PixelFormat.kUnknown, 640, 400, 120, 240, 1));
+ videoModes.add(new FPSRatedVideoMode(PixelFormat.kUnknown, 1280, 800, 120, 120, 1));
} else {
if (sensorModel == LibCameraJNI.SensorModel.IMX477) {
@@ -100,13 +101,13 @@ public class LibcameraGpuSettables extends VisionSourceSettables {
}
// Settings for the OV5647 sensor, which is used by the Pi Camera Module v1
- videoModes.put(0, new FPSRatedVideoMode(PixelFormat.kUnknown, 320, 240, 90, 90, 1));
- videoModes.put(1, new FPSRatedVideoMode(PixelFormat.kUnknown, 640, 480, 85, 90, 1));
- videoModes.put(2, new FPSRatedVideoMode(PixelFormat.kUnknown, 960, 720, 45, 49, 0.74));
+ videoModes.add(new FPSRatedVideoMode(PixelFormat.kUnknown, 320, 240, 90, 90, 1));
+ videoModes.add(new FPSRatedVideoMode(PixelFormat.kUnknown, 640, 480, 85, 90, 1));
+ videoModes.add(new FPSRatedVideoMode(PixelFormat.kUnknown, 960, 720, 45, 49, 0.74));
// Half the size of the active areas on the OV5647
- videoModes.put(3, new FPSRatedVideoMode(PixelFormat.kUnknown, 2592 / 2, 1944 / 2, 20, 20, 1));
- videoModes.put(4, new FPSRatedVideoMode(PixelFormat.kUnknown, 1280, 720, 30, 45, 0.91));
- videoModes.put(5, new FPSRatedVideoMode(PixelFormat.kUnknown, 1920, 1080, 15, 20, 0.72));
+ videoModes.add(new FPSRatedVideoMode(PixelFormat.kUnknown, 2592 / 2, 1944 / 2, 20, 20, 1));
+ videoModes.add(new FPSRatedVideoMode(PixelFormat.kUnknown, 1280, 720, 30, 45, 0.91));
+ videoModes.add(new FPSRatedVideoMode(PixelFormat.kUnknown, 1920, 1080, 15, 20, 0.72));
}
// TODO need to add more video modes for new sensors here
@@ -253,7 +254,7 @@ public class LibcameraGpuSettables extends VisionSourceSettables {
}
@Override
- public HashMap getAllVideoModes() {
+ public List getAllVideoModes() {
return videoModes;
}
diff --git a/photon-core/src/main/java/org/photonvision/vision/pipeline/AdvancedPipelineSettings.java b/photon-core/src/main/java/org/photonvision/vision/pipeline/AdvancedPipelineSettings.java
index b42c650ba..0c54fa7ad 100644
--- a/photon-core/src/main/java/org/photonvision/vision/pipeline/AdvancedPipelineSettings.java
+++ b/photon-core/src/main/java/org/photonvision/vision/pipeline/AdvancedPipelineSettings.java
@@ -17,7 +17,7 @@
package org.photonvision.vision.pipeline;
-import com.fasterxml.jackson.annotation.JsonAnySetter;
+import io.avaje.jsonb.Json;
import java.util.Objects;
import org.opencv.core.Point;
import org.photonvision.common.util.numbers.DoubleCouple;
@@ -91,19 +91,16 @@ public class AdvancedPipelineSettings extends CVPipelineSettings {
public int cornerDetectionSideCount = 4;
public double cornerDetectionAccuracyPercentage = 10;
+ // MIGRATION: 2025
/**
* Handles backward compatibility for the deprecated outputShowMultipleTargets property. When
* outputShowMultipleTargets is encountered during deserialization, it sets outputMaximumTargets
* appropriately. If outputShowMultipleTargets is false, outputMaximumTargets is set to 1.
*/
- @JsonAnySetter
- public void handleUnknownProperty(String name, Object value) {
- // Handle the old showMultipleTargets property for backward compatibility
- if ("outputShowMultipleTargets".equals(name) && value instanceof Boolean showMultipleTargets) {
- if (!showMultipleTargets) {
- // If showMultipleTargets is false, limit to 1 target
- outputMaximumTargets = 1;
- }
+ @Json.Property("outputShowMultipleTargets")
+ public void importShowMultipleTargets(boolean showMultipleTargets) {
+ if (!showMultipleTargets) {
+ outputMaximumTargets = 1;
}
}
diff --git a/photon-core/src/main/java/org/photonvision/vision/pipeline/AprilTagPipelineSettings.java b/photon-core/src/main/java/org/photonvision/vision/pipeline/AprilTagPipelineSettings.java
index 0c4b05475..7423e9d74 100644
--- a/photon-core/src/main/java/org/photonvision/vision/pipeline/AprilTagPipelineSettings.java
+++ b/photon-core/src/main/java/org/photonvision/vision/pipeline/AprilTagPipelineSettings.java
@@ -17,11 +17,9 @@
package org.photonvision.vision.pipeline;
-import com.fasterxml.jackson.annotation.JsonTypeName;
import org.photonvision.vision.apriltag.AprilTagFamily;
import org.photonvision.vision.target.TargetModel;
-@JsonTypeName("AprilTagPipelineSettings")
public class AprilTagPipelineSettings extends AdvancedPipelineSettings {
public AprilTagFamily tagFamily = AprilTagFamily.kTag36h11;
public int decimate = 1;
diff --git a/photon-core/src/main/java/org/photonvision/vision/pipeline/ArucoPipelineSettings.java b/photon-core/src/main/java/org/photonvision/vision/pipeline/ArucoPipelineSettings.java
index 7d7d6ea88..b78792a1a 100644
--- a/photon-core/src/main/java/org/photonvision/vision/pipeline/ArucoPipelineSettings.java
+++ b/photon-core/src/main/java/org/photonvision/vision/pipeline/ArucoPipelineSettings.java
@@ -17,12 +17,10 @@
package org.photonvision.vision.pipeline;
-import com.fasterxml.jackson.annotation.JsonTypeName;
import org.photonvision.common.util.numbers.IntegerCouple;
import org.photonvision.vision.apriltag.AprilTagFamily;
import org.photonvision.vision.target.TargetModel;
-@JsonTypeName("ArucoPipelineSettings")
public class ArucoPipelineSettings extends AdvancedPipelineSettings {
public AprilTagFamily tagFamily = AprilTagFamily.kTag36h11;
diff --git a/photon-core/src/main/java/org/photonvision/vision/pipeline/CVPipelineSettings.java b/photon-core/src/main/java/org/photonvision/vision/pipeline/CVPipelineSettings.java
index 24eea55d9..ce372ed9f 100644
--- a/photon-core/src/main/java/org/photonvision/vision/pipeline/CVPipelineSettings.java
+++ b/photon-core/src/main/java/org/photonvision/vision/pipeline/CVPipelineSettings.java
@@ -17,23 +17,25 @@
package org.photonvision.vision.pipeline;
-import com.fasterxml.jackson.annotation.JsonSubTypes;
-import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import io.avaje.jsonb.Json;
+import io.avaje.jsonb.JsonType;
+import io.avaje.jsonb.Jsonb;
+import io.avaje.jsonb.Types;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
import java.util.Objects;
import org.photonvision.vision.frame.FrameDivisor;
import org.photonvision.vision.opencv.ImageRotationMode;
-@JsonTypeInfo(
- use = JsonTypeInfo.Id.NAME,
- include = JsonTypeInfo.As.WRAPPER_ARRAY,
- property = "type")
-@JsonSubTypes({
- @JsonSubTypes.Type(value = ColoredShapePipelineSettings.class),
- @JsonSubTypes.Type(value = ReflectivePipelineSettings.class),
- @JsonSubTypes.Type(value = DriverModePipelineSettings.class),
- @JsonSubTypes.Type(value = AprilTagPipelineSettings.class),
- @JsonSubTypes.Type(value = ArucoPipelineSettings.class),
- @JsonSubTypes.Type(value = ObjectDetectionPipelineSettings.class)
+@Json(typeProperty = "type")
+@Json.SubTypes({
+ @Json.SubType(type = ColoredShapePipelineSettings.class),
+ @Json.SubType(type = ReflectivePipelineSettings.class),
+ @Json.SubType(type = DriverModePipelineSettings.class),
+ @Json.SubType(type = AprilTagPipelineSettings.class),
+ @Json.SubType(type = ArucoPipelineSettings.class),
+ @Json.SubType(type = ObjectDetectionPipelineSettings.class)
})
public class CVPipelineSettings implements Cloneable {
public int pipelineIndex = 0;
@@ -153,4 +155,22 @@ public class CVPipelineSettings implements Cloneable {
+ outputShouldShow
+ '}';
}
+
+ // MIGRATION: 2026
+ public static String remapSettingsJson(String pipelineJson) {
+ final JsonType> objListJsonb = Jsonb.instance().type(Types.listOf(Object.class));
+ final JsonType> objMapJsonb =
+ Jsonb.instance().type(Types.mapOf(Object.class));
+
+ List pipelineMigrationIn = objListJsonb.fromJson(pipelineJson);
+
+ @SuppressWarnings("unchecked")
+ var pipelineData = (Map) pipelineMigrationIn.get(1);
+
+ Map pipelineMigrationOut = new HashMap<>();
+ pipelineMigrationOut.putAll(pipelineData);
+ pipelineMigrationOut.put("type", pipelineMigrationIn.get(0));
+
+ return objMapJsonb.toJson(pipelineMigrationOut);
+ }
}
diff --git a/photon-core/src/main/java/org/photonvision/vision/pipeline/ColoredShapePipelineSettings.java b/photon-core/src/main/java/org/photonvision/vision/pipeline/ColoredShapePipelineSettings.java
index b7f7d9d06..473ebb042 100644
--- a/photon-core/src/main/java/org/photonvision/vision/pipeline/ColoredShapePipelineSettings.java
+++ b/photon-core/src/main/java/org/photonvision/vision/pipeline/ColoredShapePipelineSettings.java
@@ -17,14 +17,12 @@
package org.photonvision.vision.pipeline;
-import com.fasterxml.jackson.annotation.JsonTypeName;
import java.util.Objects;
import org.photonvision.common.util.numbers.DoubleCouple;
import org.photonvision.common.util.numbers.IntegerCouple;
import org.photonvision.vision.calibration.CameraCalibrationCoefficients;
import org.photonvision.vision.opencv.ContourShape;
-@JsonTypeName("ColoredShapePipelineSettings")
public class ColoredShapePipelineSettings extends AdvancedPipelineSettings {
public ContourShape contourShape = ContourShape.Triangle;
public DoubleCouple contourPerimeter = new DoubleCouple(0, Double.MAX_VALUE);
diff --git a/photon-core/src/main/java/org/photonvision/vision/pipeline/DriverModePipelineSettings.java b/photon-core/src/main/java/org/photonvision/vision/pipeline/DriverModePipelineSettings.java
index e09b107d5..4847b3bfb 100644
--- a/photon-core/src/main/java/org/photonvision/vision/pipeline/DriverModePipelineSettings.java
+++ b/photon-core/src/main/java/org/photonvision/vision/pipeline/DriverModePipelineSettings.java
@@ -17,11 +17,11 @@
package org.photonvision.vision.pipeline;
-import com.fasterxml.jackson.annotation.JsonTypeName;
+import io.avaje.jsonb.Json;
import org.photonvision.common.util.numbers.DoubleCouple;
import org.photonvision.vision.processes.PipelineManager;
-@JsonTypeName("DriverModePipelineSettings")
+@Json
public class DriverModePipelineSettings extends CVPipelineSettings {
public DoubleCouple offsetPoint = new DoubleCouple();
public boolean crosshair = true;
diff --git a/photon-core/src/main/java/org/photonvision/vision/pipeline/FocusPipelineSettings.java b/photon-core/src/main/java/org/photonvision/vision/pipeline/FocusPipelineSettings.java
index 619f7947a..bd8d861ba 100644
--- a/photon-core/src/main/java/org/photonvision/vision/pipeline/FocusPipelineSettings.java
+++ b/photon-core/src/main/java/org/photonvision/vision/pipeline/FocusPipelineSettings.java
@@ -17,10 +17,8 @@
package org.photonvision.vision.pipeline;
-import com.fasterxml.jackson.annotation.JsonTypeName;
import org.photonvision.vision.processes.PipelineManager;
-@JsonTypeName("FocusPipelineSettings")
public class FocusPipelineSettings extends CVPipelineSettings {
public FocusPipelineSettings() {
super();
diff --git a/photon-core/src/main/java/org/photonvision/vision/pipeline/ReflectivePipelineSettings.java b/photon-core/src/main/java/org/photonvision/vision/pipeline/ReflectivePipelineSettings.java
index 21add81c4..074469b20 100644
--- a/photon-core/src/main/java/org/photonvision/vision/pipeline/ReflectivePipelineSettings.java
+++ b/photon-core/src/main/java/org/photonvision/vision/pipeline/ReflectivePipelineSettings.java
@@ -17,9 +17,6 @@
package org.photonvision.vision.pipeline;
-import com.fasterxml.jackson.annotation.JsonTypeName;
-
-@JsonTypeName("ReflectivePipelineSettings")
public class ReflectivePipelineSettings extends AdvancedPipelineSettings {
public double contourFilterRangeX = 2;
public double contourFilterRangeY = 2;
diff --git a/photon-core/src/main/java/org/photonvision/vision/pipeline/UICalibrationData.java b/photon-core/src/main/java/org/photonvision/vision/pipeline/UICalibrationData.java
index 59842ba3a..21a049ca6 100644
--- a/photon-core/src/main/java/org/photonvision/vision/pipeline/UICalibrationData.java
+++ b/photon-core/src/main/java/org/photonvision/vision/pipeline/UICalibrationData.java
@@ -17,8 +17,10 @@
package org.photonvision.vision.pipeline;
+import io.avaje.jsonb.Json;
import org.opencv.objdetect.Objdetect;
+@Json
public class UICalibrationData {
public int videoModeIndex;
public int count;
@@ -53,11 +55,18 @@ public class UICalibrationData {
this.tagFamily = tagFamily;
}
+ @Json
public enum BoardType {
CHESSBOARD,
- CHARUCOBOARD,
+ CHARUCOBOARD;
+
+ @Json.Value
+ int toValue() {
+ return ordinal();
+ }
}
+ @Json
public enum TagFamily {
Dict_4X4_1000(Objdetect.DICT_4X4_1000),
Dict_5X5_1000(Objdetect.DICT_5X5_1000),
@@ -75,6 +84,11 @@ public class UICalibrationData {
private TagFamily(int value) {
this.value = value;
}
+
+ @Json.Value
+ int toValue() {
+ return ordinal();
+ }
}
@Override
diff --git a/photon-core/src/main/java/org/photonvision/vision/processes/VisionModule.java b/photon-core/src/main/java/org/photonvision/vision/processes/VisionModule.java
index b4a568930..6ae1b8a9f 100644
--- a/photon-core/src/main/java/org/photonvision/vision/processes/VisionModule.java
+++ b/photon-core/src/main/java/org/photonvision/vision/processes/VisionModule.java
@@ -22,6 +22,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
+import java.util.Map;
import java.util.function.BiConsumer;
import org.opencv.core.Size;
import org.photonvision.common.configuration.CameraConfiguration;
@@ -591,23 +592,23 @@ public class VisionModule {
ret.fpsLimit = this.fpsLimit;
// TODO refactor into helper method
- var temp = new HashMap>();
+ var temp = new ArrayList>();
var videoModes = visionSource.getSettables().getAllVideoModes();
- for (var k : videoModes.entrySet()) {
+ for (var videoMode : videoModes) {
var internalMap = new HashMap();
- internalMap.put("width", k.getValue().width);
- internalMap.put("height", k.getValue().height);
- internalMap.put("fps", k.getValue().fps);
+ internalMap.put("width", videoMode.width);
+ internalMap.put("height", videoMode.height);
+ internalMap.put("fps", videoMode.fps);
internalMap.put(
"pixelFormat",
- ((k.getValue() instanceof LibcameraGpuSource.FPSRatedVideoMode)
+ ((videoMode instanceof LibcameraGpuSource.FPSRatedVideoMode)
? "kPicam"
- : k.getValue().pixelFormat.toString())
+ : videoMode.pixelFormat.toString())
.substring(1)); // Remove the k prefix
- temp.put(k.getKey(), internalMap);
+ temp.add(internalMap);
}
if (videoModes.size() == 0) {
@@ -728,7 +729,7 @@ public class VisionModule {
public void addCalibrationToConfig(CameraCalibrationCoefficients newCalibration) {
if (newCalibration != null) {
- logger.info("Got new calibration for " + newCalibration.unrotatedImageSize);
+ logger.info("Got new calibration for " + newCalibration.resolution);
visionSource.getSettables().addCalibration(newCalibration);
} else {
logger.error("Got null calibration?");
@@ -737,9 +738,9 @@ public class VisionModule {
saveAndBroadcastAll();
}
- public void removeCalibrationFromConfig(Size unrotatedImageSize) {
- if (unrotatedImageSize != null) {
- visionSource.getSettables().removeCalibration(unrotatedImageSize);
+ public void removeCalibrationFromConfig(Size resolution) {
+ if (resolution != null) {
+ visionSource.getSettables().removeCalibration(resolution);
} else {
logger.error("Got null size?");
}
diff --git a/photon-core/src/main/java/org/photonvision/vision/processes/VisionModuleChangeSubscriber.java b/photon-core/src/main/java/org/photonvision/vision/processes/VisionModuleChangeSubscriber.java
index c98c128d1..18688f07c 100644
--- a/photon-core/src/main/java/org/photonvision/vision/processes/VisionModuleChangeSubscriber.java
+++ b/photon-core/src/main/java/org/photonvision/vision/processes/VisionModuleChangeSubscriber.java
@@ -17,7 +17,7 @@
package org.photonvision.vision.processes;
-import com.fasterxml.jackson.databind.ObjectMapper;
+import io.avaje.jsonb.Jsonb;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
@@ -30,7 +30,6 @@ import org.photonvision.common.dataflow.events.DataChangeEvent;
import org.photonvision.common.dataflow.events.IncomingWebSocketEvent;
import org.photonvision.common.logging.LogGroup;
import org.photonvision.common.logging.Logger;
-import org.photonvision.common.util.file.JacksonUtils;
import org.photonvision.common.util.numbers.DoubleCouple;
import org.photonvision.common.util.numbers.IntegerCouple;
import org.photonvision.vision.calibration.CameraCalibrationCoefficients;
@@ -57,10 +56,10 @@ public class VisionModuleChangeSubscriber extends DataChangeSubscriber {
}
@Override
- public void onDataChangeEvent(DataChangeEvent> event) {
+ public void onDataChangeEvent(DataChangeEvent event) {
// Camera index -1 means a "multicast event" (i.e. the event is received by all
// cameras)
- if (event instanceof IncomingWebSocketEvent wsEvent
+ if (event instanceof IncomingWebSocketEvent wsEvent
&& wsEvent.cameraUniqueName != null
&& wsEvent.cameraUniqueName.equals(parentModule.uniqueName())) {
logger.trace("Got PSC event - propName: " + wsEvent.propertyName);
@@ -68,7 +67,7 @@ public class VisionModuleChangeSubscriber extends DataChangeSubscriber {
try {
getSettingChanges()
.add(
- new VisionModuleChange(
+ new VisionModuleChange(
wsEvent.propertyName,
wsEvent.data,
parentModule.pipelineManager.getCurrentPipeline().getSettings(),
@@ -92,6 +91,10 @@ public class VisionModuleChangeSubscriber extends DataChangeSubscriber {
var newPropValue = change.getNewPropValue();
var currentSettings = change.getCurrentSettings();
var originContext = change.getOriginContext();
+ if (newPropValue instanceof Long) {
+ newPropValue = ((Long) newPropValue).intValue();
+ }
+
switch (propName) {
case "pipelineName" -> newPipelineNickname((String) newPropValue);
case "newPipelineInfo" -> newPipelineInfo((Pair) newPropValue);
@@ -199,7 +202,7 @@ public class VisionModuleChangeSubscriber extends DataChangeSubscriber {
public void startCalibration(Map data) {
try {
- var deserialized = JacksonUtils.deserialize(data, UICalibrationData.class);
+ var deserialized = Jsonb.instance().type(UICalibrationData.class).fromObject(data);
parentModule.startCalibration(deserialized);
parentModule.saveAndBroadcastAll();
} catch (Exception e) {
@@ -295,8 +298,8 @@ public class VisionModuleChangeSubscriber extends DataChangeSubscriber {
}
} else if (propField.getType() == ModelProperties.class
&& newPropValue instanceof LinkedHashMap) {
- ObjectMapper mapper = new ObjectMapper();
- ModelProperties modelProps = mapper.convertValue(newPropValue, ModelProperties.class);
+ ModelProperties modelProps =
+ Jsonb.instance().type(ModelProperties.class).fromObject(newPropValue);
propField.set(currentSettings, modelProps);
} else {
propField.set(currentSettings, newPropValue);
diff --git a/photon-core/src/main/java/org/photonvision/vision/processes/VisionSourceManager.java b/photon-core/src/main/java/org/photonvision/vision/processes/VisionSourceManager.java
index 22d1f43f2..10245889d 100644
--- a/photon-core/src/main/java/org/photonvision/vision/processes/VisionSourceManager.java
+++ b/photon-core/src/main/java/org/photonvision/vision/processes/VisionSourceManager.java
@@ -17,6 +17,7 @@
package org.photonvision.vision.processes;
+import io.avaje.jsonb.Json;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -72,7 +73,7 @@ public class VisionSourceManager {
return SingletonHolder.INSTANCE;
}
- // Jackson does use these members even if your IDE claims otherwise
+ @Json
public static class VisionSourceManagerState {
public List disabledConfigs;
public List allConnectedCameras;
diff --git a/photon-core/src/main/java/org/photonvision/vision/processes/VisionSourceSettables.java b/photon-core/src/main/java/org/photonvision/vision/processes/VisionSourceSettables.java
index e22dc1200..a2ba77ae2 100644
--- a/photon-core/src/main/java/org/photonvision/vision/processes/VisionSourceSettables.java
+++ b/photon-core/src/main/java/org/photonvision/vision/processes/VisionSourceSettables.java
@@ -17,7 +17,8 @@
package org.photonvision.vision.processes;
-import java.util.HashMap;
+import java.util.ArrayList;
+import java.util.List;
import org.opencv.core.Size;
import org.photonvision.common.configuration.CameraConfiguration;
import org.photonvision.common.logging.LogGroup;
@@ -38,7 +39,7 @@ public abstract class VisionSourceSettables {
}
protected FrameStaticProperties frameStaticProperties = null;
- protected HashMap videoModes = new HashMap<>();
+ protected List videoModes = new ArrayList<>();
public CameraConfiguration getConfiguration() {
return configuration;
@@ -99,12 +100,11 @@ public abstract class VisionSourceSettables {
protected abstract void setVideoModeInternal(VideoMode videoMode);
- @SuppressWarnings("unused")
public void setVideoModeIndex(int index) {
setVideoMode(videoModes.get(index));
}
- public abstract HashMap getAllVideoModes();
+ public abstract List getAllVideoModes();
public double getFOV() {
return configuration.FOV;
@@ -121,8 +121,8 @@ public abstract class VisionSourceSettables {
calculateFrameStaticProps();
}
- public void removeCalibration(Size unrotatedImageSize) {
- configuration.removeCalibration(unrotatedImageSize);
+ public void removeCalibration(Size resolution) {
+ configuration.removeCalibration(resolution);
calculateFrameStaticProps();
}
@@ -135,8 +135,8 @@ public abstract class VisionSourceSettables {
configuration.calibrations.stream()
.filter(
it ->
- it.unrotatedImageSize.width == videoMode.width
- && it.unrotatedImageSize.height == videoMode.height)
+ it.resolution.width == videoMode.width
+ && it.resolution.height == videoMode.height)
.findFirst()
.orElse(null));
}
diff --git a/photon-core/src/main/java/org/photonvision/vision/target/TargetModel.java b/photon-core/src/main/java/org/photonvision/vision/target/TargetModel.java
index b53ea6ff9..0cf8ad1eb 100644
--- a/photon-core/src/main/java/org/photonvision/vision/target/TargetModel.java
+++ b/photon-core/src/main/java/org/photonvision/vision/target/TargetModel.java
@@ -17,10 +17,7 @@
package org.photonvision.vision.target;
-import com.fasterxml.jackson.annotation.JsonAlias;
-import com.fasterxml.jackson.annotation.JsonCreator;
-import com.fasterxml.jackson.annotation.JsonIgnore;
-import com.fasterxml.jackson.annotation.JsonProperty;
+import io.avaje.jsonb.Json;
import java.util.ArrayList;
import java.util.List;
import org.opencv.core.MatOfPoint3f;
@@ -49,6 +46,7 @@ import org.wpilib.math.util.Units;
*
* AprilTag models are currently only used for drawing on the output stream.
*/
+@Json
public enum TargetModel implements Releasable {
k2016HighGoal(
List.of(
@@ -129,7 +127,8 @@ public enum TargetModel implements Releasable {
-Units.inchesToMeters(16.25) / 2)),
0),
// 2023 AprilTag, with 6 inch marker width (inner black square).
- @JsonAlias({"k6in_16h5"})
+ // MIGRATION: 2023
+ @Json.Alias({"k6in_16h5"})
kAprilTag6in_16h5(
// Corners of the tag's inner black square (excluding white border)
List.of(
@@ -139,7 +138,8 @@ public enum TargetModel implements Releasable {
new Point3(Units.inchesToMeters(3), -Units.inchesToMeters(3), 0)),
Units.inchesToMeters(3 * 2)),
// 2024 AprilTag, with 6.5 inch marker width (inner black square).
- @JsonAlias({"k6p5in_36h11", "k200mmAprilTag", "kAruco6p5in_36h11"})
+ // MIGRATION: 2023
+ @Json.Alias({"k6p5in_36h11", "k200mmAprilTag", "kAruco6p5in_36h11"})
kAprilTag6p5in_36h11(
// Corners of the tag's inner black square (excluding white border)
List.of(
@@ -149,18 +149,13 @@ public enum TargetModel implements Releasable {
new Point3(-Units.inchesToMeters(6.5 / 2.0), -Units.inchesToMeters(6.5 / 2.0), 0)),
Units.inchesToMeters(6.5));
- @JsonIgnore private MatOfPoint3f realWorldTargetCoordinates;
- @JsonIgnore private final MatOfPoint3f visualizationBoxBottom = new MatOfPoint3f();
- @JsonIgnore private final MatOfPoint3f visualizationBoxTop = new MatOfPoint3f();
+ @Json.Ignore private final MatOfPoint3f realWorldTargetCoordinates;
+ @Json.Ignore private final MatOfPoint3f visualizationBoxBottom = new MatOfPoint3f();
+ @Json.Ignore private final MatOfPoint3f visualizationBoxTop = new MatOfPoint3f();
- @JsonProperty("realWorldCoordinatesArray")
private List realWorldCoordinatesArray;
-
- @JsonProperty("boxHeight")
private double boxHeight;
- TargetModel() {}
-
TargetModel(MatOfPoint3f realWorldTargetCoordinates, double boxHeight) {
this.realWorldTargetCoordinates = realWorldTargetCoordinates;
this.realWorldCoordinatesArray = realWorldTargetCoordinates.toList();
@@ -176,11 +171,8 @@ public enum TargetModel implements Releasable {
this.visualizationBoxTop.fromList(topList);
}
- @JsonCreator
- TargetModel(
- @JsonProperty(value = "realWorldCoordinatesArray") List points,
- @JsonProperty(value = "boxHeight") double boxHeight) {
- this(listToMat(points), boxHeight);
+ TargetModel(List realWorldCoordinatesArray, double boxHeight) {
+ this(listToMat(realWorldCoordinatesArray), boxHeight);
}
public List getRealWorldCoordinatesArray() {
@@ -227,6 +219,12 @@ public enum TargetModel implements Releasable {
// return new TargetModel(corners, 0);
// }
+ @Json.Value
+ @Override
+ public String toString() {
+ return super.toString();
+ }
+
@Override
public void release() {
realWorldTargetCoordinates.release();
diff --git a/photon-core/src/test/java/org/photonvision/common/configuration/ConfigTest.java b/photon-core/src/test/java/org/photonvision/common/configuration/ConfigTest.java
index ad5249e4f..e8c088d4b 100644
--- a/photon-core/src/test/java/org/photonvision/common/configuration/ConfigTest.java
+++ b/photon-core/src/test/java/org/photonvision/common/configuration/ConfigTest.java
@@ -20,8 +20,8 @@ package org.photonvision.common.configuration;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import io.avaje.jsonb.Jsonb;
import java.io.File;
-import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -32,7 +32,6 @@ import org.photonvision.common.LoadJNI;
import org.photonvision.common.logging.LogGroup;
import org.photonvision.common.logging.LogLevel;
import org.photonvision.common.logging.Logger;
-import org.photonvision.common.util.file.JacksonUtils;
import org.photonvision.vision.camera.PVCameraInfo;
import org.photonvision.vision.pipeline.AprilTagPipelineSettings;
import org.photonvision.vision.pipeline.CVPipelineSettings;
@@ -135,60 +134,31 @@ public class ConfigTest {
}
@Test
- public void testJacksonHandlesOldVersions() throws IOException {
- var str =
+ public void testJsonbHandlesOldVersions() throws IOException {
+ var json =
"{\"baseName\":\"aaaaaa\",\"uniqueName\":\"aaaaaa\",\"nickname\":\"aaaaaa\",\"FOV\":70.0,\"path\":\"dev/vid\",\"cameraType\":\"UsbCamera\",\"currentPipelineIndex\":0,\"camPitch\":{\"radians\":0.0},\"calibrations\":[], \"cameraLEDs\":[]}";
- File tempFile = File.createTempFile("test", ".json");
- tempFile.deleteOnExit();
- var writer = new FileWriter(tempFile);
- writer.write(str);
- writer.flush();
- writer.close();
- CameraConfiguration result =
- JacksonUtils.deserialize(tempFile.toPath(), CameraConfiguration.class);
- tempFile.delete();
+ CameraConfiguration result = Jsonb.instance().type(CameraConfiguration.class).fromJson(json);
}
@Test
- public void testJacksonAddUSBVIDPID() throws IOException {
- var str =
+ public void testJsonbAddUSBVIDPID() throws IOException {
+ var json =
"{\"baseName\":\"aaaaaa\",\"uniqueName\":\"aaaaaa\",\"nickname\":\"aaaaaa\",\"FOV\":70.0,\"path\":\"dev/vid\",\"cameraType\":\"UsbCamera\",\"currentPipelineIndex\":0,\"camPitch\":{\"radians\":0.0},\"calibrations\":[], \"usbVID\":3, \"usbPID\":4, \"cameraLEDs\":[]}";
- File tempFile = File.createTempFile("test", ".json");
- tempFile.deleteOnExit();
- var writer = new FileWriter(tempFile);
- writer.write(str);
- writer.flush();
- writer.close();
- try {
- CameraConfiguration result =
- JacksonUtils.deserialize(tempFile.toPath(), CameraConfiguration.class);
- String ser = JacksonUtils.serializeToString(result);
- System.out.println(ser);
- } catch (Exception e) {
- e.printStackTrace();
- }
-
- tempFile.delete();
+ CameraConfiguration result = Jsonb.instance().type(CameraConfiguration.class).fromJson(json);
+ String ser = Jsonb.instance().toJson(result);
+ System.out.println(ser);
}
@Test
- public void testJacksonHandlesOldTargetEnum() throws IOException {
- var str = "[ \"AprilTagPipelineSettings\", {\n \"targetModel\" : \"k6in_16h5\"\n} ]\n";
+ public void testJsonbHandlesOldTargetEnum() throws IOException {
+ var json = "[ \"AprilTagPipelineSettings\", {\n \"targetModel\" : \"k6in_16h5\"\n} ]\n";
- File tempFile = File.createTempFile("test", ".json");
- tempFile.deleteOnExit();
- var writer = new FileWriter(tempFile);
- writer.write(str);
- writer.flush();
- writer.close();
+ json = CVPipelineSettings.remapSettingsJson(json);
AprilTagPipelineSettings settings =
- (AprilTagPipelineSettings)
- JacksonUtils.deserialize(tempFile.toPath(), CVPipelineSettings.class);
+ (AprilTagPipelineSettings) Jsonb.instance().type(CVPipelineSettings.class).fromJson(json);
assertEquals(TargetModel.kAprilTag6in_16h5, settings.targetModel);
-
- tempFile.delete();
}
}
diff --git a/photon-core/src/test/java/org/photonvision/common/configuration/NetworkConfigTest.java b/photon-core/src/test/java/org/photonvision/common/configuration/NetworkConfigTest.java
index cd4d5e1f6..83e39ec17 100644
--- a/photon-core/src/test/java/org/photonvision/common/configuration/NetworkConfigTest.java
+++ b/photon-core/src/test/java/org/photonvision/common/configuration/NetworkConfigTest.java
@@ -20,8 +20,11 @@ package org.photonvision.common.configuration;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
-import com.fasterxml.jackson.databind.ObjectMapper;
+import io.avaje.jsonb.JsonType;
+import io.avaje.jsonb.Jsonb;
import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Path;
import org.junit.jupiter.api.Test;
@@ -30,26 +33,30 @@ import org.photonvision.common.util.TestUtils;
public class NetworkConfigTest {
@Test
public void testSerialization() throws IOException {
- var mapper = new ObjectMapper();
var path = Path.of("netTest.json");
- mapper.writeValue(path.toFile(), new NetworkConfig());
- assertDoesNotThrow(() -> mapper.readValue(path.toFile(), NetworkConfig.class));
+ JsonType jsonb = Jsonb.instance().type(NetworkConfig.class);
+ try (var outputStream = new FileOutputStream(path.toFile())) {
+ jsonb.toJson(new NetworkConfig(), outputStream);
+ }
+ try (var inputStream = new FileInputStream(path.toFile())) {
+ assertDoesNotThrow(() -> jsonb.fromJson(inputStream));
+ }
new File("netTest.json").delete();
}
@Test
public void testDeserializeTeamNumberOrNtServerAddress() {
{
- var folder = TestUtils.getResourcesFolderPath(true).resolve("network-old-team-number");
+ var folder = TestUtils.getResourcesFolderPath(true).resolve("network-team-number");
var configMgr = new ConfigManager(folder, new LegacyConfigProvider(folder));
configMgr.load();
assertEquals("9999", configMgr.getConfig().getNetworkConfig().ntServerAddress);
}
{
- var folder = TestUtils.getResourcesFolderPath(true).resolve("network-new-team-number");
+ var folder = TestUtils.getResourcesFolderPath(true).resolve("network-ip-addr");
var configMgr = new ConfigManager(folder, new LegacyConfigProvider(folder));
configMgr.load();
- assertEquals("9999", configMgr.getConfig().getNetworkConfig().ntServerAddress);
+ assertEquals("127.0.0.1", configMgr.getConfig().getNetworkConfig().ntServerAddress);
}
}
}
diff --git a/photon-core/src/test/java/org/photonvision/common/configuration/NeuralNetworkPropertyManagerTest.java b/photon-core/src/test/java/org/photonvision/common/configuration/NeuralNetworkPropertyManagerTest.java
index 14dd6db0e..e499a5fb3 100644
--- a/photon-core/src/test/java/org/photonvision/common/configuration/NeuralNetworkPropertyManagerTest.java
+++ b/photon-core/src/test/java/org/photonvision/common/configuration/NeuralNetworkPropertyManagerTest.java
@@ -20,13 +20,14 @@ package org.photonvision.common.configuration;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import io.avaje.jsonb.JsonType;
+import io.avaje.jsonb.Jsonb;
import java.nio.file.Path;
import java.util.LinkedList;
import org.junit.jupiter.api.Test;
import org.photonvision.common.configuration.NeuralNetworkModelManager.Family;
import org.photonvision.common.configuration.NeuralNetworkModelManager.Version;
import org.photonvision.common.configuration.NeuralNetworkModelsSettings.ModelProperties;
-import org.photonvision.common.util.file.JacksonUtils;
public class NeuralNetworkPropertyManagerTest {
@Test
@@ -42,10 +43,12 @@ public class NeuralNetworkPropertyManagerTest {
640,
Family.RKNN,
Version.YOLOV8));
- String result = assertDoesNotThrow(() -> JacksonUtils.serializeToString(nnpm));
- var deserializedNnpm =
- assertDoesNotThrow(
- () -> JacksonUtils.deserialize(result, NeuralNetworkModelsSettings.class));
+ JsonType jsonb =
+ Jsonb.instance().type(NeuralNetworkModelsSettings.class);
+ String result = assertDoesNotThrow(() -> jsonb.toJson(nnpm));
+ System.out.println(result);
+ var deserializedNnpm = assertDoesNotThrow(() -> jsonb.fromJson(result));
+ assertEquals(nnpm.getModels().length, deserializedNnpm.getModels().length);
assertEquals(nnpm.getModels()[0], deserializedNnpm.getModels()[0]);
}
}
diff --git a/photon-core/src/test/java/org/photonvision/common/configuration/SQLConfigTest.java b/photon-core/src/test/java/org/photonvision/common/configuration/SQLConfigTest.java
index 4995cbcee..068c4c871 100644
--- a/photon-core/src/test/java/org/photonvision/common/configuration/SQLConfigTest.java
+++ b/photon-core/src/test/java/org/photonvision/common/configuration/SQLConfigTest.java
@@ -19,8 +19,9 @@ package org.photonvision.common.configuration;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assumptions.assumeTrue;
-import com.fasterxml.jackson.core.JsonProcessingException;
+import io.avaje.json.JsonDataException;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Collection;
@@ -32,8 +33,8 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.photonvision.common.LoadJNI;
import org.photonvision.common.configuration.NeuralNetworkModelManager.Family;
+import org.photonvision.common.hardware.Platform;
import org.photonvision.common.util.TestUtils;
-import org.photonvision.vision.camera.CameraQuirk;
import org.photonvision.vision.camera.PVCameraInfo;
import org.photonvision.vision.pipeline.AdvancedPipelineSettings;
import org.photonvision.vision.pipeline.AprilTagPipelineSettings;
@@ -92,32 +93,6 @@ public class SQLConfigTest {
assertEquals(cfgLoader.getConfig().getNetworkConfig().ntServerAddress, "5940");
}
- @Test
- public void testLoad2024_3_1() throws IOException {
- // Copy the 2024.3.1 config to a temp dir
- FileUtils.copyDirectory(
- TestUtils.getConfigDirectoriesPath(false)
- .resolve("photonvision_config_from_v2024.3.1")
- .toFile(),
- tmpDir.resolve("photonvision_config_from_v2024.3.1").toFile());
-
- var cfgLoader = new SqlConfigProvider(tmpDir.resolve("photonvision_config_from_v2024.3.1"));
-
- assertDoesNotThrow(cfgLoader::load);
-
- System.out.println(cfgLoader.getConfig());
- for (var c : CameraQuirk.values()) {
- assertDoesNotThrow(
- () ->
- cfgLoader
- .config
- .getCameraConfigurations()
- .get("Microsoft_LifeCam_HD-3000")
- .cameraQuirks
- .hasQuirk(c));
- }
- }
-
void common2025p3p1Assertions(PhotonConfiguration config) {
// Make sure we got 8 cameras
assertEquals(8, config.getCameraConfigurations().size());
@@ -134,7 +109,7 @@ public class SQLConfigTest {
}
@Test
- public void testLoadNewNNMM() throws JsonProcessingException, IOException {
+ public void testLoadNewNNMM() throws JsonDataException, IOException {
var folder = tmpDir.resolve("2025.3.1-old-nnmm");
FileUtils.copyDirectory(
TestUtils.getConfigDirectoriesPath(false).resolve("2025.3.1-old-nnmm").toFile(),
@@ -165,7 +140,7 @@ public class SQLConfigTest {
common2025p3p1Assertions(reloadedProvider.getConfig());
// And make sure NNPM has all 5 models
- assertEquals(5, reloadedProvider.getConfig().neuralNetworkPropertyManager().getModels().length);
+ assertEquals(5, reloadedProvider.getConfig().getNeuralNetworkProperties().getModels().length);
ConfigManager.INSTANCE = null;
}
@@ -210,4 +185,31 @@ public class SQLConfigTest {
ConfigManager.INSTANCE = null;
}
+
+ @Test
+ public void testV2026p3p4WindowsPaths() throws JsonDataException, IOException {
+ assumeTrue(
+ Platform.isWindows(), "This test is only relevant on Windows, skipping on other platforms");
+
+ var configName = "2026.3.4-windows";
+ var folder = tmpDir.resolve(configName);
+ FileUtils.copyDirectory(
+ TestUtils.getConfigDirectoriesPath(false).resolve(configName).toFile(), folder.toFile());
+
+ var cfgManager = new ConfigManager(folder, new SqlConfigProvider(folder));
+
+ cfgManager.load();
+
+ // Make sure we have calibrated 1280x720, and the board observation paths matches
+ var camCfg =
+ cfgManager
+ .getConfig()
+ .getCameraConfigurations()
+ .get("1414304b-6812-487a-ab5c-89ee70704fae");
+ assertEquals(1280, camCfg.calibrations.get(0).resolution.width);
+ assertEquals(720, camCfg.calibrations.get(0).resolution.height);
+ assertEquals(
+ "C:\\Users\\matth\\Documents\\GitHub\\photonvision\\test\\photonvision_config\\calibration\\1414304b-6812-487a-ab5c-89ee70704fae\\imgs\\1280x720\\img0.png",
+ camCfg.calibrations.get(0).observations.get(0).snapshotDataLocation.toString());
+ }
}
diff --git a/photon-core/src/test/java/org/photonvision/hardware/HardwareConfigTest.java b/photon-core/src/test/java/org/photonvision/hardware/HardwareConfigTest.java
index 42aaf97f9..16ece6bb0 100644
--- a/photon-core/src/test/java/org/photonvision/hardware/HardwareConfigTest.java
+++ b/photon-core/src/test/java/org/photonvision/hardware/HardwareConfigTest.java
@@ -22,7 +22,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import com.diozero.internal.spi.NativeDeviceFactoryInterface;
-import com.fasterxml.jackson.databind.ObjectMapper;
+import io.avaje.jsonb.Jsonb;
+import java.io.FileInputStream;
import java.io.IOException;
import org.junit.jupiter.api.Test;
import org.photonvision.common.configuration.HardwareConfig;
@@ -33,10 +34,9 @@ import org.photonvision.common.util.TestUtils;
public class HardwareConfigTest {
@Test
public void loadJson() {
- try {
- System.out.println("Loading Hardware configs...");
- var config =
- new ObjectMapper().readValue(TestUtils.getHardwareConfigJson(), HardwareConfig.class);
+ System.out.println("Loading Hardware configs...");
+ try (var stream = new FileInputStream(TestUtils.getHardwareConfigJson())) {
+ var config = Jsonb.instance().type(HardwareConfig.class).fromJson(stream);
assertEquals(config.deviceName, "PhotonVision");
// Ensure defaults are not null
assertArrayEquals(config.ledPins.stream().mapToInt(i -> i).toArray(), new int[] {2, 13});
diff --git a/photon-core/src/test/java/org/photonvision/hardware/HardwareTest.java b/photon-core/src/test/java/org/photonvision/hardware/HardwareTest.java
index fcff442b7..f75c2c593 100644
--- a/photon-core/src/test/java/org/photonvision/hardware/HardwareTest.java
+++ b/photon-core/src/test/java/org/photonvision/hardware/HardwareTest.java
@@ -22,7 +22,8 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
import com.diozero.internal.provider.builtin.DefaultDeviceFactory;
import com.diozero.internal.spi.NativeDeviceFactoryInterface;
-import com.fasterxml.jackson.databind.ObjectMapper;
+import io.avaje.jsonb.Jsonb;
+import java.io.FileInputStream;
import java.io.IOException;
import java.util.HashSet;
import java.util.List;
@@ -68,8 +69,9 @@ public class HardwareTest {
@BeforeEach
void setup() throws IOException {
System.out.println("Loading Hardware configs...");
- hardwareConfig =
- new ObjectMapper().readValue(TestUtils.getHardwareConfigJson(), HardwareConfig.class);
+ try (var stream = new FileInputStream(TestUtils.getHardwareConfigJson())) {
+ hardwareConfig = Jsonb.instance().type(HardwareConfig.class).fromJson(stream);
+ }
deviceFactory = HardwareManager.configureCustomGPIO(hardwareConfig);
}
diff --git a/photon-core/src/test/java/org/photonvision/vision/pipeline/CalibrationRotationPipeTest.java b/photon-core/src/test/java/org/photonvision/vision/pipeline/CalibrationRotationPipeTest.java
index f0f9d482a..9785dc5ba 100644
--- a/photon-core/src/test/java/org/photonvision/vision/pipeline/CalibrationRotationPipeTest.java
+++ b/photon-core/src/test/java/org/photonvision/vision/pipeline/CalibrationRotationPipeTest.java
@@ -128,10 +128,7 @@ public class CalibrationRotationPipeTest {
FrameStaticProperties frameProps =
new FrameStaticProperties(
- (int) coeffs.unrotatedImageSize.width,
- (int) coeffs.unrotatedImageSize.height,
- 66,
- coeffs);
+ (int) coeffs.resolution.width, (int) coeffs.resolution.height, 66, coeffs);
FrameStaticProperties rotatedFrameProps = frameProps.rotate(rot);
@@ -210,7 +207,7 @@ public class CalibrationRotationPipeTest {
double[] rotatedCamMat = {fx, 0, res.width - cx, 0, fy, res.height - cy, 0, 0, 1};
assertArrayEquals(rotatedCamMat, coeffs2.cameraIntrinsics.data);
// AND the image size should be the same
- assertEquals(res, coeffs2.unrotatedImageSize);
+ assertEquals(res, coeffs2.resolution);
// WHEN the camera calibration is rotated 180 degrees
var coeffs3 = coeffs2.rotateCoefficients(rot);
@@ -218,7 +215,7 @@ public class CalibrationRotationPipeTest {
// THEN the camera matrix will be the same as the original
assertArrayEquals(intrinsics, coeffs3.cameraIntrinsics.data);
// AND the image size should be the same
- assertEquals(res, coeffs2.unrotatedImageSize);
+ assertEquals(res, coeffs2.resolution);
}
@CartesianTest
diff --git a/photon-core/src/test/java/org/photonvision/vision/processes/VisionModuleManagerTest.java b/photon-core/src/test/java/org/photonvision/vision/processes/VisionModuleManagerTest.java
index 63add51bd..d36c0acf9 100644
--- a/photon-core/src/test/java/org/photonvision/vision/processes/VisionModuleManagerTest.java
+++ b/photon-core/src/test/java/org/photonvision/vision/processes/VisionModuleManagerTest.java
@@ -21,8 +21,8 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
+import java.util.ArrayList;
import java.util.Arrays;
-import java.util.HashMap;
import java.util.List;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
@@ -115,9 +115,9 @@ public class VisionModuleManagerTest {
}
@Override
- public HashMap getAllVideoModes() {
- var ret = new HashMap();
- ret.put(0, getCurrentVideoMode());
+ public List getAllVideoModes() {
+ var ret = new ArrayList();
+ ret.add(getCurrentVideoMode());
return ret;
}
diff --git a/photon-core/src/test/java/org/photonvision/vision/processes/VisionSourceManagerTest.java b/photon-core/src/test/java/org/photonvision/vision/processes/VisionSourceManagerTest.java
index 83114e320..7c82ed66a 100644
--- a/photon-core/src/test/java/org/photonvision/vision/processes/VisionSourceManagerTest.java
+++ b/photon-core/src/test/java/org/photonvision/vision/processes/VisionSourceManagerTest.java
@@ -21,6 +21,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import io.avaje.jsonb.Jsonb;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@@ -31,8 +32,8 @@ import org.junit.jupiter.api.Test;
import org.photonvision.common.LoadJNI;
import org.photonvision.common.configuration.CameraConfiguration;
import org.photonvision.common.configuration.ConfigManager;
+import org.photonvision.common.configuration.PhotonConfiguration;
import org.photonvision.common.util.TestUtils;
-import org.photonvision.common.util.file.JacksonUtils;
import org.photonvision.vision.camera.PVCameraInfo;
import org.wpilib.vision.camera.UsbCameraInfo;
@@ -94,17 +95,18 @@ public class VisionSourceManagerTest {
7,
8));
- var str = JacksonUtils.serializeToString(usb);
+ var str = Jsonb.instance().type(PVCameraInfo.class).toJson(usb);
System.out.println(str);
- System.out.println(JacksonUtils.deserialize(str, PVCameraInfo.class));
+ System.out.println(Jsonb.instance().type(PVCameraInfo.class).fromJson(str));
}
{
var csi =
PVCameraInfo.fromCSICameraInfo(
"/dev/v4l/by-path/platform-1f00110000.csi-video-index0", "rp1-cfe");
- var str = JacksonUtils.serializeToString(csi);
+
+ var str = Jsonb.instance().type(PVCameraInfo.class).toJson(csi);
System.out.println(str);
- System.out.println(JacksonUtils.deserialize(str, PVCameraInfo.class));
+ System.out.println(Jsonb.instance().type(PVCameraInfo.class).fromJson(str));
}
}
@@ -137,7 +139,10 @@ public class VisionSourceManagerTest {
vsm.assignUnmatchedCamera(fileCamera1);
- System.out.println(JacksonUtils.serializeToString(ConfigManager.getInstance().getConfig()));
+ System.out.println(
+ Jsonb.instance()
+ .type(PhotonConfiguration.class)
+ .toJson(ConfigManager.getInstance().getConfig()));
// And make assertions about the current matching state
assertEquals(1, vsm.getVsmState().allConnectedCameras.size());
@@ -268,7 +273,10 @@ public class VisionSourceManagerTest {
vsm.assignUnmatchedCamera(fileCamera3);
- System.out.println(JacksonUtils.serializeToString(ConfigManager.getInstance().getConfig()));
+ System.out.println(
+ Jsonb.instance()
+ .type(PhotonConfiguration.class)
+ .toJson(ConfigManager.getInstance().getConfig()));
// And make assertions about the current matching state
assertEquals(3, vsm.getVsmState().allConnectedCameras.size());
diff --git a/photon-lib/src/main/java/org/photonvision/simulation/SimCameraProperties.java b/photon-lib/src/main/java/org/photonvision/simulation/SimCameraProperties.java
index 7f602f6f5..09f769f39 100644
--- a/photon-lib/src/main/java/org/photonvision/simulation/SimCameraProperties.java
+++ b/photon-lib/src/main/java/org/photonvision/simulation/SimCameraProperties.java
@@ -24,7 +24,10 @@
package org.photonvision.simulation;
-import com.fasterxml.jackson.databind.ObjectMapper;
+import io.avaje.json.JsonIoException;
+import io.avaje.jsonb.Json;
+import io.avaje.jsonb.Jsonb;
+import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
@@ -66,6 +69,26 @@ import org.wpilib.math.util.Pair;
* network latency and the latency reported will always be perfect.
*/
public class SimCameraProperties {
+ @Json
+ record SimCameraData(SimCameraCalibrationData[] calibrations) {
+ @Json
+ record SimCameraCalibrationData(
+ ResolutionData resolution,
+ CameraIntrinsicsData cameraIntrinsics,
+ DistortionCoefficients distCoeffs,
+ double[] perViewErrors,
+ double standardDeviation) {
+ @Json
+ record ResolutionData(int width, int height) {}
+
+ @Json
+ record CameraIntrinsicsData(double[] data) {}
+
+ @Json
+ record DistortionCoefficients(double[] data) {}
+ }
+ }
+
private final Random rand = new Random();
// calibration
private int resWidth;
@@ -114,47 +137,26 @@ public class SimCameraProperties {
* calibrated resolution.
*/
public SimCameraProperties(Path path, int width, int height) throws IOException {
- var mapper = new ObjectMapper();
- var json = mapper.readTree(path.toFile());
- json = json.get("calibrations");
+ SimCameraData data;
+ try (var stream = new FileInputStream(path.toFile())) {
+ data = Jsonb.instance().type(SimCameraData.class).fromJson(stream);
+ } catch (JsonIoException e) {
+ throw new IOException("Invalid calibration JSON", e);
+ }
boolean success = false;
- try {
- for (int i = 0; i < json.size() && !success; i++) {
- // check if this calibration entry is our desired resolution
- var calib = json.get(i);
- int jsonWidth = calib.get("resolution").get("width").asInt();
- int jsonHeight = calib.get("resolution").get("height").asInt();
- if (jsonWidth != width || jsonHeight != height) continue;
- // get the relevant calibration values
- var jsonIntrinsicsNode = calib.get("cameraIntrinsics").get("data");
- double[] jsonIntrinsics = new double[jsonIntrinsicsNode.size()];
- for (int j = 0; j < jsonIntrinsicsNode.size(); j++) {
- jsonIntrinsics[j] = jsonIntrinsicsNode.get(j).asDouble();
- }
- var jsonDistortNode = calib.get("distCoeffs").get("data");
- double[] jsonDistortion = new double[8];
- Arrays.fill(jsonDistortion, 0);
- for (int j = 0; j < jsonDistortNode.size(); j++) {
- jsonDistortion[j] = jsonDistortNode.get(j).asDouble();
- }
- var jsonViewErrors = calib.get("perViewErrors");
- double jsonAvgError = 0;
- for (int j = 0; j < jsonViewErrors.size(); j++) {
- jsonAvgError += jsonViewErrors.get(j).asDouble();
- }
- jsonAvgError /= jsonViewErrors.size();
- double jsonErrorStdDev = calib.get("standardDeviation").asDouble();
- // assign the read JSON values to this CameraProperties
- setCalibration(
- jsonWidth,
- jsonHeight,
- MatBuilder.fill(Nat.N3(), Nat.N3(), jsonIntrinsics),
- MatBuilder.fill(Nat.N8(), Nat.N1(), jsonDistortion));
- setCalibError(jsonAvgError, jsonErrorStdDev);
- success = true;
- }
- } catch (Exception e) {
- throw new IOException("Invalid calibration JSON");
+ for (var calib : data.calibrations) {
+ // check if this calibration entry is our desired resolution
+ if (calib.resolution.width != width || calib.resolution.height != height) continue;
+ // get the relevant calibration values
+ double avgViewError = Arrays.stream(calib.perViewErrors).average().orElse(0);
+ // assign the read JSON values to this CameraProperties
+ setCalibration(
+ calib.resolution.width,
+ calib.resolution.height,
+ MatBuilder.fill(Nat.N3(), Nat.N3(), calib.cameraIntrinsics.data),
+ MatBuilder.fill(Nat.N8(), Nat.N1(), calib.distCoeffs.data));
+ setCalibError(avgViewError, calib.standardDeviation);
+ success = true;
}
if (!success) throw new IOException("Requested resolution not found in calibration");
}
diff --git a/photon-server/build.gradle b/photon-server/build.gradle
index 51955adf9..5df27ea1d 100644
--- a/photon-server/build.gradle
+++ b/photon-server/build.gradle
@@ -39,6 +39,7 @@ shadowJar {
configurations = [
project.configurations.runtimeClasspath
]
+ mergeServiceFiles()
}
node {
diff --git a/photon-server/src/main/java/org/photonvision/Main.java b/photon-server/src/main/java/org/photonvision/Main.java
index 13df4b7dd..a69bfc603 100644
--- a/photon-server/src/main/java/org/photonvision/Main.java
+++ b/photon-server/src/main/java/org/photonvision/Main.java
@@ -235,6 +235,8 @@ public class Main {
Logger.setLevel(LogGroup.General, logLevel);
logger.info("Logging initialized in debug mode.");
+ System.setProperty("jsonb.disableAdapterSpi", "true");
+
logger.info(
"Starting PhotonVision version "
+ PhotonVersion.versionString
diff --git a/photon-server/src/main/java/org/photonvision/server/DataSocketHandler.java b/photon-server/src/main/java/org/photonvision/server/DataSocketHandler.java
index 8d15c8d55..f34a17405 100644
--- a/photon-server/src/main/java/org/photonvision/server/DataSocketHandler.java
+++ b/photon-server/src/main/java/org/photonvision/server/DataSocketHandler.java
@@ -17,22 +17,21 @@
package org.photonvision.server;
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.core.type.TypeReference;
-import com.fasterxml.jackson.databind.ObjectMapper;
+import io.avaje.json.JsonException;
+import io.avaje.jsonb.Json;
+import io.avaje.jsonb.Jsonb;
+import io.avaje.jsonb.jackson.JacksonAdapter;
import io.javalin.websocket.WsBinaryMessageContext;
import io.javalin.websocket.WsCloseContext;
import io.javalin.websocket.WsConnectContext;
import io.javalin.websocket.WsContext;
-import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.time.Duration;
-import java.util.ArrayList;
-import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
+import org.jetbrains.annotations.Nullable;
import org.msgpack.jackson.dataformat.MessagePackFactory;
import org.photonvision.common.dataflow.DataChangeDestination;
import org.photonvision.common.dataflow.DataChangeService;
@@ -47,7 +46,9 @@ import org.wpilib.math.util.Pair;
public class DataSocketHandler {
private final Logger logger = new Logger(DataSocketHandler.class, LogGroup.WebServer);
private final List users = new CopyOnWriteArrayList<>();
- private final ObjectMapper objectMapper = new ObjectMapper(new MessagePackFactory());
+ private final JacksonAdapter adapter =
+ JacksonAdapter.builder().jsonFactory(new MessagePackFactory()).serializeEmpty(true).build();
+ private final Jsonb msgpackJsonb = Jsonb.builder().adapter(adapter).build();
private final DataChangeService dcService = DataChangeService.getInstance();
@SuppressWarnings("FieldCanBeLocal")
@@ -91,20 +92,16 @@ public class DataSocketHandler {
}
}
+ @Json
+ static record WSMessage(
+ @Nullable String cameraUniqueName, @Json.Unmapped Map properties) {}
+
@SuppressWarnings({"unchecked"})
public void onBinaryMessage(WsBinaryMessageContext context) {
try {
- Map deserializedData =
- objectMapper.readValue(context.data(), new TypeReference<>() {});
+ var message = msgpackJsonb.type(WSMessage.class).fromJson(context.data());
- // Special case the current camera index
- String cameraUniqueName = "";
- if (deserializedData.get("cameraUniqueName") instanceof String camUniqueNameValue) {
- cameraUniqueName = camUniqueNameValue;
- deserializedData.remove("cameraUniqueName");
- }
-
- for (Map.Entry entry : deserializedData.entrySet()) {
+ for (Map.Entry entry : message.properties.entrySet()) {
try {
var entryKey = entry.getKey();
var entryValue = entry.getValue();
@@ -131,7 +128,7 @@ public class DataSocketHandler {
DataChangeDestination.DCD_ACTIVEMODULE,
"isDriverMode",
(Boolean) entryValue,
- cameraUniqueName,
+ message.cameraUniqueName,
context));
case SMT_CHANGECAMERANAME ->
dcService.publishEvent(
@@ -139,7 +136,7 @@ public class DataSocketHandler {
DataChangeDestination.DCD_ACTIVEMODULE,
"cameraNickname",
(String) entryValue,
- cameraUniqueName,
+ message.cameraUniqueName,
context));
case SMT_CHANGEPIPELINENAME ->
dcService.publishEvent(
@@ -147,38 +144,39 @@ public class DataSocketHandler {
DataChangeDestination.DCD_ACTIVEMODULE,
"pipelineName",
(String) entryValue,
- cameraUniqueName,
+ message.cameraUniqueName,
context));
case SMT_ADDNEWPIPELINE -> {
// HashMap data = (HashMap) entryValue;
// var type = (PipelineType) data.get("pipelineType");
// var name = (String) data.get("pipelineName");
- var arr = (ArrayList) entryValue;
+ var arr = (List) entryValue;
var name = (String) arr.get(0);
- var type = PipelineType.values()[(Integer) arr.get(1) + 3];
+ var type = PipelineType.values()[((Long) arr.get(1)).intValue() + 3];
dcService.publishEvent(
new IncomingWebSocketEvent<>(
DataChangeDestination.DCD_ACTIVEMODULE,
"newPipelineInfo",
Pair.of(name, type),
- cameraUniqueName,
+ message.cameraUniqueName,
context));
}
case SMT_CHANGEBRIGHTNESS ->
HardwareManager.getInstance()
.setBrightnessPercent(Integer.parseInt(entryValue.toString()));
case SMT_DUPLICATEPIPELINE -> {
- var pipeIndex = (Integer) entryValue;
+ var pipeIndex = ((Long) entryValue).intValue();
- logger.info("Duplicating pipe@index" + pipeIndex + " for camera " + cameraUniqueName);
+ logger.info(
+ "Duplicating pipe@index" + pipeIndex + " for camera " + message.cameraUniqueName);
dcService.publishEvent(
new IncomingWebSocketEvent<>(
DataChangeDestination.DCD_ACTIVEMODULE,
"duplicatePipeline",
pipeIndex,
- cameraUniqueName,
+ message.cameraUniqueName,
context));
}
case SMT_DELETECURRENTPIPELINE ->
@@ -187,27 +185,29 @@ public class DataSocketHandler {
DataChangeDestination.DCD_ACTIVEMODULE,
"deleteCurrPipeline",
0,
- cameraUniqueName,
+ message.cameraUniqueName,
context));
case SMT_ROBOTOFFSETPOINT ->
dcService.publishEvent(
new IncomingWebSocketEvent<>(
DataChangeDestination.DCD_ACTIVEMODULE,
"robotOffsetPoint",
- (Integer) entryValue,
- cameraUniqueName,
+ ((Long) entryValue).intValue(),
+ message.cameraUniqueName,
null));
case SMT_CURRENTCAMERA ->
dcService.publishEvent(
new IncomingWebSocketEvent<>(
- DataChangeDestination.DCD_OTHER, "changeUICamera", (Integer) entryValue));
+ DataChangeDestination.DCD_OTHER,
+ "changeUICamera",
+ ((Long) entryValue).intValue()));
case SMT_CURRENTPIPELINE ->
dcService.publishEvent(
new IncomingWebSocketEvent<>(
DataChangeDestination.DCD_ACTIVEMODULE,
"changePipeline",
- (Integer) entryValue,
- cameraUniqueName,
+ ((Long) entryValue).intValue(),
+ message.cameraUniqueName,
context));
case SMT_STARTPNPCALIBRATION ->
dcService.publishEvent(
@@ -215,7 +215,7 @@ public class DataSocketHandler {
DataChangeDestination.DCD_ACTIVEMODULE,
"startCalibration",
(Map) entryValue,
- cameraUniqueName,
+ message.cameraUniqueName,
context));
case SMT_SAVEINPUTSNAPSHOT ->
dcService.publishEvent(
@@ -223,7 +223,7 @@ public class DataSocketHandler {
DataChangeDestination.DCD_ACTIVEMODULE,
"saveInputSnapshot",
0,
- cameraUniqueName,
+ message.cameraUniqueName,
context));
case SMT_SAVEOUTPUTSNAPSHOT ->
dcService.publishEvent(
@@ -231,7 +231,7 @@ public class DataSocketHandler {
DataChangeDestination.DCD_ACTIVEMODULE,
"saveOutputSnapshot",
0,
- cameraUniqueName,
+ message.cameraUniqueName,
context));
case SMT_TAKECALIBRATIONSNAPSHOT ->
dcService.publishEvent(
@@ -239,10 +239,10 @@ public class DataSocketHandler {
DataChangeDestination.DCD_ACTIVEMODULE,
"takeCalSnapshot",
0,
- cameraUniqueName,
+ message.cameraUniqueName,
context));
case SMT_PIPELINESETTINGCHANGE -> {
- HashMap data = (HashMap) entryValue;
+ Map data = (Map) entryValue;
if (data.size() >= 2) {
var cameraIndex2 = (String) data.get("cameraUniqueName");
@@ -267,28 +267,27 @@ public class DataSocketHandler {
new IncomingWebSocketEvent<>(
DataChangeDestination.DCD_ACTIVEMODULE,
"changePipelineType",
- (Integer) entryValue,
- cameraUniqueName,
+ ((Long) entryValue).intValue(),
+ message.cameraUniqueName,
context));
}
} catch (Exception e) {
logger.error("Failed to parse message!", e);
}
}
- } catch (IOException e) {
+ } catch (IllegalStateException | JsonException e) {
logger.error("Failed to deserialize message!", e);
}
}
- private void sendMessage(ByteBuffer b, WsContext user) throws JsonProcessingException {
+ private void sendMessage(ByteBuffer b, WsContext user) {
if (user.session.isOpen()) {
user.send(b);
}
}
- public void broadcastMessage(Object message, WsContext userToSkip)
- throws JsonProcessingException {
- ByteBuffer b = ByteBuffer.wrap(objectMapper.writeValueAsBytes(message));
+ public void broadcastMessage(Object message, WsContext userToSkip) throws JsonException {
+ ByteBuffer b = ByteBuffer.wrap(msgpackJsonb.toJsonBytes(message));
if (userToSkip == null) {
for (WsContext user : users) {
diff --git a/photon-server/src/main/java/org/photonvision/server/DataSocketMessageType.java b/photon-server/src/main/java/org/photonvision/server/DataSocketMessageType.java
index f5222cf37..c277e8cc7 100644
--- a/photon-server/src/main/java/org/photonvision/server/DataSocketMessageType.java
+++ b/photon-server/src/main/java/org/photonvision/server/DataSocketMessageType.java
@@ -21,7 +21,6 @@ import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
-@SuppressWarnings("unused")
public enum DataSocketMessageType {
SMT_DRIVERMODE("driverMode"),
SMT_CHANGECAMERANAME("changeCameraName"),
diff --git a/photon-server/src/main/java/org/photonvision/server/RequestHandler.java b/photon-server/src/main/java/org/photonvision/server/RequestHandler.java
index c6ed9b8d2..0cd5380b0 100644
--- a/photon-server/src/main/java/org/photonvision/server/RequestHandler.java
+++ b/photon-server/src/main/java/org/photonvision/server/RequestHandler.java
@@ -17,8 +17,9 @@
package org.photonvision.server;
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.ObjectMapper;
+import io.avaje.json.JsonException;
+import io.avaje.jsonb.Json;
+import io.avaje.jsonb.Jsonb;
import io.javalin.http.Context;
import io.javalin.http.UploadedFile;
import java.io.*;
@@ -53,7 +54,6 @@ import org.photonvision.common.logging.Logger;
import org.photonvision.common.networking.NetworkManager;
import org.photonvision.common.util.ShellExec;
import org.photonvision.common.util.TimedTaskManager;
-import org.photonvision.common.util.file.JacksonUtils;
import org.photonvision.common.util.file.ProgramDirectoryUtilities;
import org.photonvision.vision.calibration.CameraCalibrationCoefficients;
import org.photonvision.vision.camera.CameraQuirk;
@@ -71,8 +71,6 @@ public class RequestHandler {
private static final Logger logger = new Logger(RequestHandler.class, LogGroup.WebServer);
- private static final ObjectMapper kObjectMapper = new ObjectMapper();
-
private static boolean testMode = false;
public static void onStatusRequest(Context ctx) {
@@ -84,7 +82,8 @@ public class RequestHandler {
testMode = isTestMode;
}
- private record CommonCameraUniqueName(String cameraUniqueName) {}
+ @Json
+ record CommonCameraUniqueName(String cameraUniqueName) {}
public static void onSettingsImportRequest(Context ctx) {
var file = ctx.uploadedFile("data");
@@ -372,12 +371,12 @@ public class RequestHandler {
public static void onGeneralSettingsRequest(Context ctx) {
NetworkConfig config;
try {
- config = kObjectMapper.readValue(ctx.bodyInputStream(), NetworkConfig.class);
+ config = Jsonb.instance().type(NetworkConfig.class).fromJson(ctx.bodyInputStream());
ctx.status(200);
ctx.result("Successfully saved general settings");
logger.info("Successfully saved general settings");
- } catch (IOException e) {
+ } catch (IllegalStateException | JsonException e) {
// If the settings can't be parsed, use the default network settings
config = new NetworkConfig();
@@ -394,13 +393,14 @@ public class RequestHandler {
NetworkTablesManager.getInstance().setConfig(config);
}
- private record CameraSettingsRequest(
+ @Json
+ record CameraSettingsRequest(
double fov, HashMap quirksToChange, String cameraUniqueName) {}
public static void onCameraSettingsRequest(Context ctx) {
try {
CameraSettingsRequest request =
- kObjectMapper.readValue(ctx.body(), CameraSettingsRequest.class);
+ Jsonb.instance().type(CameraSettingsRequest.class).fromJson(ctx.body());
// Extract the settings from the request
double fov = request.fov;
HashMap quirksToChange = request.quirksToChange;
@@ -489,7 +489,7 @@ public class RequestHandler {
try {
CommonCameraUniqueName request =
- kObjectMapper.readValue(ctx.body(), CommonCameraUniqueName.class);
+ Jsonb.instance().type(CommonCameraUniqueName.class).fromJson(ctx.body());
var calData =
VisionSourceManager.getInstance()
@@ -509,7 +509,7 @@ public class RequestHandler {
ctx.result("Camera calibration successfully completed!");
ctx.status(200);
logger.info("Camera calibration successfully completed!");
- } catch (JsonProcessingException e) {
+ } catch (IllegalStateException | JsonException e) {
ctx.status(400);
ctx.result(
"The 'cameraUniqueName' field was not found in the request. Please make sure the cameraUniqueName of the vision module is specified with the 'cameraUniqueName' key.");
@@ -523,13 +523,14 @@ public class RequestHandler {
}
}
- private record DataCalibrationImportRequest(
+ @Json
+ record DataCalibrationImportRequest(
String cameraUniqueName, CameraCalibrationCoefficients calibration) {}
public static void onDataCalibrationImportRequest(Context ctx) {
- try {
+ try (var stream = ctx.req().getInputStream()) {
DataCalibrationImportRequest request =
- kObjectMapper.readValue(ctx.req().getInputStream(), DataCalibrationImportRequest.class);
+ Jsonb.instance().type(DataCalibrationImportRequest.class).fromJson(stream);
var uploadCalibrationEvent =
new IncomingWebSocketEvent<>(
@@ -543,7 +544,7 @@ public class RequestHandler {
ctx.status(200);
ctx.result("Calibration imported successfully from imported data!");
logger.info("Calibration imported successfully from imported data!");
- } catch (JsonProcessingException e) {
+ } catch (IllegalStateException | JsonException e) {
ctx.status(400);
ctx.result("The provided calibration data was malformed");
logger.error("The provided calibration data was malformed", e);
@@ -704,11 +705,10 @@ public class RequestHandler {
}
ConfigManager.getInstance()
.getConfig()
- .neuralNetworkPropertyManager()
+ .getNeuralNetworkProperties()
.addModelProperties(modelProperties);
- logger.debug(
- ConfigManager.getInstance().getConfig().neuralNetworkPropertyManager().toString());
+ logger.debug(ConfigManager.getInstance().getConfig().getNeuralNetworkProperties().toString());
NeuralNetworkModelManager.getInstance().discoverModels();
@@ -860,14 +860,15 @@ public class RequestHandler {
UIPhotonConfiguration.programStateToUi(ConfigManager.getInstance().getConfig())));
}
- private record DeleteObjectDetectionModelRequest(Path modelPath) {}
+ @Json
+ record DeleteObjectDetectionModelRequest(Path modelPath) {}
public static void onDeleteObjectDetectionModelRequest(Context ctx) {
logger.info("Deleting object detection model");
try {
DeleteObjectDetectionModelRequest request =
- JacksonUtils.deserialize(ctx.body(), DeleteObjectDetectionModelRequest.class);
+ Jsonb.instance().type(DeleteObjectDetectionModelRequest.class).fromJson(ctx.body());
if (request.modelPath == null) {
ctx.status(400);
@@ -892,7 +893,7 @@ public class RequestHandler {
if (!ConfigManager.getInstance()
.getConfig()
- .neuralNetworkPropertyManager()
+ .getNeuralNetworkProperties()
.removeModel(request.modelPath)) {
ctx.status(400);
ctx.result("The model's information was not found in the config");
@@ -917,12 +918,13 @@ public class RequestHandler {
}
}
- private record RenameObjectDetectionModelRequest(Path modelPath, String newName) {}
+ @Json
+ record RenameObjectDetectionModelRequest(Path modelPath, String newName) {}
public static void onRenameObjectDetectionModelRequest(Context ctx) {
try {
RenameObjectDetectionModelRequest request =
- JacksonUtils.deserialize(ctx.body(), RenameObjectDetectionModelRequest.class);
+ Jsonb.instance().type(RenameObjectDetectionModelRequest.class).fromJson(ctx.body());
if (request.modelPath == null) {
ctx.status(400);
@@ -947,7 +949,7 @@ public class RequestHandler {
if (!ConfigManager.getInstance()
.getConfig()
- .neuralNetworkPropertyManager()
+ .getNeuralNetworkProperties()
.renameModel(request.modelPath, request.newName)) {
ctx.status(400);
ctx.result("The model's information was not found in the config");
@@ -994,12 +996,13 @@ public class RequestHandler {
ctx.status(HardwareManager.getInstance().restartDevice() ? 204 : 500);
}
- private record CameraNicknameChangeRequest(String name, String cameraUniqueName) {}
+ @Json
+ record CameraNicknameChangeRequest(String name, String cameraUniqueName) {}
public static void onCameraNicknameChangeRequest(Context ctx) {
try {
CameraNicknameChangeRequest request =
- kObjectMapper.readValue(ctx.body(), CameraNicknameChangeRequest.class);
+ Jsonb.instance().type(CameraNicknameChangeRequest.class).fromJson(ctx.body());
VisionSourceManager.getInstance()
.vmm
@@ -1008,7 +1011,7 @@ public class RequestHandler {
ctx.status(200);
ctx.result("Successfully changed the camera name to: " + request.name);
logger.info("Successfully changed the camera name to: " + request.name);
- } catch (JsonProcessingException e) {
+ } catch (IllegalStateException | JsonException e) {
ctx.status(400).result("Invalid JSON format");
logger.error("Failed to process camera nickname change request", e);
} catch (Exception e) {
@@ -1038,8 +1041,8 @@ public class RequestHandler {
module.getStateAsCameraConfig().calibrations.stream()
.filter(
it ->
- Math.abs(it.unrotatedImageSize.width - width) < 1e-4
- && Math.abs(it.unrotatedImageSize.height - height) < 1e-4)
+ Math.abs(it.resolution.width - width) < 1e-4
+ && Math.abs(it.resolution.height - height) < 1e-4)
.findFirst()
.orElse(null);
@@ -1052,12 +1055,13 @@ public class RequestHandler {
ctx.status(200);
}
- private record CalibrationRemoveRequest(int width, int height, String cameraUniqueName) {}
+ @Json
+ record CalibrationRemoveRequest(int width, int height, String cameraUniqueName) {}
public static void onCalibrationRemoveRequest(Context ctx) {
try {
CalibrationRemoveRequest request =
- kObjectMapper.readValue(ctx.body(), CalibrationRemoveRequest.class);
+ Jsonb.instance().type(CalibrationRemoveRequest.class).fromJson(ctx.body());
logger.info(
"Attempting to remove calibration for camera: "
@@ -1083,7 +1087,7 @@ public class RequestHandler {
+ request.width
+ "x"
+ request.height);
- } catch (JsonProcessingException e) {
+ } catch (IllegalStateException | JsonException e) {
ctx.status(400).result("Invalid JSON format");
logger.error("Failed to process calibration removed request", e);
} catch (Exception e) {
@@ -1107,8 +1111,8 @@ public class RequestHandler {
.stream()
.filter(
it ->
- Math.abs(it.unrotatedImageSize.width - width) < 1e-4
- && Math.abs(it.unrotatedImageSize.height - height) < 1e-4)
+ Math.abs(it.resolution.width - width) < 1e-4
+ && Math.abs(it.resolution.height - height) < 1e-4)
.findFirst()
.orElse(null);
@@ -1146,8 +1150,8 @@ public class RequestHandler {
cc.calibrations.stream()
.filter(
it ->
- Math.abs(it.unrotatedImageSize.width - width) < 1e-4
- && Math.abs(it.unrotatedImageSize.height - height) < 1e-4)
+ Math.abs(it.resolution.width - width) < 1e-4
+ && Math.abs(it.resolution.height - height) < 1e-4)
.findFirst()
.orElse(null);
@@ -1319,7 +1323,7 @@ public class RequestHandler {
public static void onNukeOneCamera(Context ctx) {
try {
CommonCameraUniqueName request =
- kObjectMapper.readValue(ctx.body(), CommonCameraUniqueName.class);
+ Jsonb.instance().type(CommonCameraUniqueName.class).fromJson(ctx.body());
logger.warn("Deleting camera name " + request.cameraUniqueName);
@@ -1334,7 +1338,7 @@ public class RequestHandler {
VisionSourceManager.getInstance().deleteVisionSource(request.cameraUniqueName);
ctx.status(200);
- } catch (IOException e) {
+ } catch (IOException | IllegalStateException | JsonException e) {
logger.error("Failed to delete camera", e);
ctx.status(500);
ctx.result("Failed to delete camera");
@@ -1346,7 +1350,7 @@ public class RequestHandler {
logger.info(ctx.queryString());
try {
CommonCameraUniqueName request =
- kObjectMapper.readValue(ctx.body(), CommonCameraUniqueName.class);
+ Jsonb.instance().type(CommonCameraUniqueName.class).fromJson(ctx.body());
if (VisionSourceManager.getInstance()
.reactivateDisabledCameraConfig(request.cameraUniqueName)) {
@@ -1354,7 +1358,7 @@ public class RequestHandler {
} else {
ctx.status(403);
}
- } catch (IOException e) {
+ } catch (IllegalStateException | JsonException e) {
ctx.status(401);
logger.error("Failed to process activate matched camera request", e);
ctx.result("Failed to process activate matched camera request");
@@ -1362,14 +1366,15 @@ public class RequestHandler {
}
}
- private record AssignUnmatchedCamera(PVCameraInfo cameraInfo) {}
+ @Json
+ record AssignUnmatchedCamera(PVCameraInfo cameraInfo) {}
public static void onAssignUnmatchedCameraRequest(Context ctx) {
logger.info(ctx.queryString());
try {
AssignUnmatchedCamera request =
- kObjectMapper.readValue(ctx.body(), AssignUnmatchedCamera.class);
+ Jsonb.instance().type(AssignUnmatchedCamera.class).fromJson(ctx.body());
if (request.cameraInfo == null) {
ctx.status(400);
@@ -1385,7 +1390,7 @@ public class RequestHandler {
}
ctx.result("Successfully assigned camera: " + request.cameraInfo);
- } catch (IOException e) {
+ } catch (IllegalStateException | JsonException e) {
ctx.status(401);
logger.error("Failed to process assign unmatched camera request", e);
ctx.result("Failed to process assign unmatched camera request");
@@ -1397,14 +1402,14 @@ public class RequestHandler {
logger.info(ctx.queryString());
try {
CommonCameraUniqueName request =
- kObjectMapper.readValue(ctx.body(), CommonCameraUniqueName.class);
+ Jsonb.instance().type(CommonCameraUniqueName.class).fromJson(ctx.body());
if (VisionSourceManager.getInstance().deactivateVisionSource(request.cameraUniqueName)) {
ctx.status(200);
} else {
ctx.status(403);
}
- } catch (IOException e) {
+ } catch (IllegalStateException | JsonException e) {
ctx.status(401);
logger.error("Failed to process unassign camera request", e);
ctx.result("Failed to process unassign camera request");
diff --git a/photon-server/src/main/java/org/photonvision/server/Server.java b/photon-server/src/main/java/org/photonvision/server/Server.java
index 573592a18..0fb75cc65 100644
--- a/photon-server/src/main/java/org/photonvision/server/Server.java
+++ b/photon-server/src/main/java/org/photonvision/server/Server.java
@@ -41,7 +41,7 @@ public class Server {
}
@Override
- public void onDataChangeEvent(DataChangeEvent> event) {
+ public void onDataChangeEvent(DataChangeEvent event) {
if (event.propertyName.equals("restartServer")) {
Server.restart();
}
diff --git a/photon-server/src/main/java/org/photonvision/server/TestRequestHandler.java b/photon-server/src/main/java/org/photonvision/server/TestRequestHandler.java
index 6f888c78a..8fccf2582 100644
--- a/photon-server/src/main/java/org/photonvision/server/TestRequestHandler.java
+++ b/photon-server/src/main/java/org/photonvision/server/TestRequestHandler.java
@@ -17,13 +17,14 @@
package org.photonvision.server;
+import io.avaje.jsonb.Json;
+import io.avaje.jsonb.Jsonb;
import io.javalin.http.Context;
import org.photonvision.common.configuration.ConfigManager;
import org.photonvision.common.configuration.NeuralNetworkModelManager;
import org.photonvision.common.hardware.Platform;
import org.photonvision.common.logging.LogGroup;
import org.photonvision.common.logging.Logger;
-import org.photonvision.common.util.file.JacksonUtils;
public class TestRequestHandler {
// Treat all 2XX calls as "INFO"
@@ -39,12 +40,13 @@ public class TestRequestHandler {
ConfigManager.getInstance().load();
}
- private record PlatformOverrideRequest(Platform platform) {}
+ @Json
+ record PlatformOverrideRequest(Platform platform) {}
public static void handlePlatformOverrideRequest(Context ctx) {
try {
PlatformOverrideRequest request =
- JacksonUtils.deserialize(ctx.body(), PlatformOverrideRequest.class);
+ Jsonb.instance().type(PlatformOverrideRequest.class).fromJson(ctx.body());
Platform platform = request.platform();
logger.info("Overriding platform to: " + platform);
diff --git a/photon-server/src/main/java/org/photonvision/server/UIInboundSubscriber.java b/photon-server/src/main/java/org/photonvision/server/UIInboundSubscriber.java
index 6d467fdd9..3fc833422 100644
--- a/photon-server/src/main/java/org/photonvision/server/UIInboundSubscriber.java
+++ b/photon-server/src/main/java/org/photonvision/server/UIInboundSubscriber.java
@@ -38,8 +38,8 @@ public class UIInboundSubscriber extends DataChangeSubscriber {
}
@Override
- public void onDataChangeEvent(DataChangeEvent> event) {
- if (event instanceof IncomingWebSocketEvent incomingWSEvent) {
+ public void onDataChangeEvent(DataChangeEvent event) {
+ if (event instanceof IncomingWebSocketEvent incomingWSEvent) {
if (incomingWSEvent.propertyName.equals("userConnected")
|| incomingWSEvent.propertyName.equals("sendFullSettings")) {
// Send full settings
diff --git a/photon-server/src/main/java/org/photonvision/server/UIOutboundSubscriber.java b/photon-server/src/main/java/org/photonvision/server/UIOutboundSubscriber.java
index 0961f1032..ceb8c4bae 100644
--- a/photon-server/src/main/java/org/photonvision/server/UIOutboundSubscriber.java
+++ b/photon-server/src/main/java/org/photonvision/server/UIOutboundSubscriber.java
@@ -17,7 +17,7 @@
package org.photonvision.server;
-import com.fasterxml.jackson.core.JsonProcessingException;
+import io.avaje.json.JsonDataException;
import java.util.Collections;
import java.util.HashMap;
import org.photonvision.common.dataflow.DataChangeDestination;
@@ -43,15 +43,15 @@ class UIOutboundSubscriber extends DataChangeSubscriber {
}
@Override
- public void onDataChangeEvent(DataChangeEvent event) {
- if (event instanceof OutgoingUIEvent thisEvent) {
+ public void onDataChangeEvent(DataChangeEvent event) {
+ if (event instanceof OutgoingUIEvent thisEvent) {
try {
if (event.data instanceof HashMap data) {
socketHandler.broadcastMessage(data, thisEvent.originContext);
} else {
socketHandler.broadcastMessage(event.data, thisEvent.originContext);
}
- } catch (JsonProcessingException e) {
+ } catch (JsonDataException e) {
logger.error("Failed to process outgoing message!", e);
}
}
diff --git a/photon-targeting/src/main/java/org/photonvision/common/networktables/PacketPublisher.java b/photon-targeting/src/main/java/org/photonvision/common/networktables/PacketPublisher.java
index db0a00271..92b4eb389 100644
--- a/photon-targeting/src/main/java/org/photonvision/common/networktables/PacketPublisher.java
+++ b/photon-targeting/src/main/java/org/photonvision/common/networktables/PacketPublisher.java
@@ -17,8 +17,8 @@
package org.photonvision.common.networktables;
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.ObjectMapper;
+import io.avaje.json.JsonException;
+import io.avaje.jsonb.Jsonb;
import java.util.HashSet;
import java.util.Set;
import org.photonvision.common.dataflow.structures.Packet;
@@ -34,12 +34,11 @@ public class PacketPublisher implements AutoCloseable {
this.publisher = publisher;
this.photonStruct = photonStruct;
- var mapper = new ObjectMapper();
try {
this.publisher
.getTopic()
- .setProperty("message_uuid", mapper.writeValueAsString(photonStruct.getInterfaceUUID()));
- } catch (JsonProcessingException e) {
+ .setProperty("message_uuid", Jsonb.instance().toJson(photonStruct.getInterfaceUUID()));
+ } catch (IllegalStateException | JsonException e) {
// TODO Auto-generated catch block
e.printStackTrace();
throw new RuntimeException(e);
diff --git a/photon-targeting/src/main/java/org/photonvision/jni/CombinedRuntimeLoader.java b/photon-targeting/src/main/java/org/photonvision/jni/CombinedRuntimeLoader.java
index b02960dea..d478e1a5c 100644
--- a/photon-targeting/src/main/java/org/photonvision/jni/CombinedRuntimeLoader.java
+++ b/photon-targeting/src/main/java/org/photonvision/jni/CombinedRuntimeLoader.java
@@ -18,7 +18,9 @@
package org.photonvision.jni;
-import com.fasterxml.jackson.databind.ObjectMapper;
+import io.avaje.jsonb.Json;
+import io.avaje.jsonb.JsonType;
+import io.avaje.jsonb.Jsonb;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
@@ -139,15 +141,18 @@ public final class CombinedRuntimeLoader {
* Architecture-specific information containing file hashes for a specific CPU architecture (e.g.,
* x86-64, arm64).
*/
+ @Json
public record ArchInfo(Map fileHashes) {}
/**
* Platform-specific information containing architectures for a specific OS platform (e.g., linux,
* windows).
*/
+ @Json
public record PlatformInfo(Map architectures) {}
/** Overall resource information to be serialized */
+ @Json
public record ResourceInformation(
// Combined MD5 hash of all native resource files
String hash,
@@ -167,10 +172,10 @@ public final class CombinedRuntimeLoader {
*/
public static List extractLibraries(Class clazz, String resourceName)
throws IOException {
- ObjectMapper mapper = new ObjectMapper();
+ JsonType jsonb = Jsonb.instance().type(ResourceInformation.class);
ResourceInformation resourceInfo;
try (var stream = clazz.getResourceAsStream(resourceName)) {
- resourceInfo = mapper.readValue(stream, ResourceInformation.class);
+ resourceInfo = jsonb.fromJson(stream);
}
var platformPath = Paths.get(getPlatformPath());
diff --git a/shared/common.gradle b/shared/common.gradle
index 587a1b2d2..5b97514c5 100644
--- a/shared/common.gradle
+++ b/shared/common.gradle
@@ -18,8 +18,8 @@ dependencies {
implementation "io.javalin:javalin:$javalinVersion"
- implementation 'org.msgpack:msgpack-core:0.9.0'
- implementation 'org.msgpack:jackson-dataformat-msgpack:0.9.0'
+ implementation "org.msgpack:msgpack-core:$msgpackVersion"
+ implementation "org.msgpack:jackson-dataformat-msgpack:$msgpackVersion"
implementation "org.wpilib.wpiutil:wpiutil-java:$wpilibVersion"
implementation "org.wpilib.datalog:datalog-java:$wpilibVersion"
@@ -34,9 +34,11 @@ dependencies {
implementation "org.wpilib.wpiunits:wpiunits-java:$wpilibVersion"
implementation wpilibTools.deps.wpilibOpenCvJava("frc" + openCVYear, openCVversion)
- implementation group: "com.fasterxml.jackson.core", name: "jackson-annotations", version: jacksonVersion
implementation group: "com.fasterxml.jackson.core", name: "jackson-core", version: jacksonVersion
implementation group: "com.fasterxml.jackson.core", name: "jackson-databind", version: jacksonVersion
+ implementation group: "io.avaje", name: "avaje-jsonb", version: avajeJsonbVersion
+ annotationProcessor group: "io.avaje", name: "avaje-jsonb-generator", version: avajeJsonbVersion
+ implementation group: "io.avaje", name: "avaje-jsonb-jackson", version: avajeJsonbVersion
implementation group: "org.ejml", name: "ejml-simple", version: ejmlVersion
implementation group: "us.hebi.quickbuf", name: "quickbuf-runtime", version: quickbufVersion;
@@ -53,6 +55,7 @@ dependencies {
test {
useJUnitPlatform()
systemProperty("java.awt.headless", !project.hasProperty("enableTestUi"))
+ systemProperty 'jsonb.disableAdapterSpi', 'true'
testLogging {
events "passed", "skipped", "failed", "standardOut", "standardError"
exceptionFormat = "full"
diff --git a/shared/javacommon.gradle b/shared/javacommon.gradle
index 56867192f..b4f7481c8 100644
--- a/shared/javacommon.gradle
+++ b/shared/javacommon.gradle
@@ -120,6 +120,7 @@ publishing {
test {
useJUnitPlatform()
systemProperty 'junit.jupiter.extensions.autodetection.enabled', 'true'
+ systemProperty 'jsonb.disableAdapterSpi', 'true'
testLogging {
events "failed"
exceptionFormat = "full"
@@ -149,9 +150,10 @@ dependencies {
implementation "org.wpilib.wpiunits:wpiunits-java:$wpilibVersion"
implementation wpilibTools.deps.wpilibOpenCvJava("frc" + openCVYear, openCVversion)
- implementation group: "com.fasterxml.jackson.core", name: "jackson-annotations", version: jacksonVersion
implementation group: "com.fasterxml.jackson.core", name: "jackson-core", version: jacksonVersion
implementation group: "com.fasterxml.jackson.core", name: "jackson-databind", version: jacksonVersion
+ implementation group: "io.avaje", name: "avaje-jsonb", version: avajeJsonbVersion
+ annotationProcessor group: "io.avaje", name: "avaje-jsonb-generator", version: avajeJsonbVersion
implementation group: "org.ejml", name: "ejml-simple", version: ejmlVersion
implementation group: "us.hebi.quickbuf", name: "quickbuf-runtime", version: quickbufVersion;
diff --git a/test-resources/network-ip-addr/networkSettings.json b/test-resources/network-ip-addr/networkSettings.json
new file mode 100644
index 000000000..699bf81f8
--- /dev/null
+++ b/test-resources/network-ip-addr/networkSettings.json
@@ -0,0 +1,3 @@
+{
+ "ntServerAddress" : "127.0.0.1"
+}
diff --git a/test-resources/network-old-team-number/networkSettings.json b/test-resources/network-old-team-number/networkSettings.json
deleted file mode 100644
index d4165c4ba..000000000
--- a/test-resources/network-old-team-number/networkSettings.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "teamNumber" : 9999
-}
diff --git a/test-resources/network-new-team-number/networkSettings.json b/test-resources/network-team-number/networkSettings.json
similarity index 100%
rename from test-resources/network-new-team-number/networkSettings.json
rename to test-resources/network-team-number/networkSettings.json
diff --git a/test-resources/old_configs/2026.3.4-windows/photon.sqlite b/test-resources/old_configs/2026.3.4-windows/photon.sqlite
new file mode 100644
index 000000000..c81de55a7
Binary files /dev/null and b/test-resources/old_configs/2026.3.4-windows/photon.sqlite differ
diff --git a/test-resources/old_configs/photonvision_config_from_v2024.3.1/photon.sqlite b/test-resources/old_configs/photonvision_config_from_v2024.3.1/photon.sqlite
deleted file mode 100644
index fe6d137a1..000000000
Binary files a/test-resources/old_configs/photonvision_config_from_v2024.3.1/photon.sqlite and /dev/null differ