Crear tipos de datos personalizados

Los widgets le permiten controlar y visualizar diferentes tipos de datos. Estos datos podrían ser integers o doubles o incluso Objetos de Java. Para visualizar estos tipos de datos utilizando widgets es útil crear una clase contenedora para estos. No es necesario crear su propia clase de Datos si el widget manejará tipos de datos con un solo campo, tales como doubles, arrays o strings.

Crear Clase de Datos

En este ejemplo, crearemos un tipo de datos personalizado para un punto 2D y sus coordenadas x e y. Para crear una clase de tipo de datos personalizada, debe extender la clase abstracta ComplexData. Su clase de datos personalizados también debe implementar el método asMap() que devuelve los datos representados como un mapa simple como se indica a continuación con la anotación @Override:

import edu.wpi.first.shuffleboard.api.data.ComplexData;

import java.util.Map;

public class MyPoint2D extends ComplexData<MyPoint2D> {

   private final double x;
   private final double y;

   //Constructor should take all the different fields needed and assign them their corresponding instance variables.
   public MyPoint2D(double x, double y) {
      this.x = x;
      this.y = y;
   }

   @Override
   public Map<String, Object> asMap() {
      return Map.of("x", x, "y", y);
   }
}

También es una buena práctica anular los métodos por defecto equals y hashcode para asegurar que diferentes objetos se consideren equivalentes cuando sus campos sean iguales. El método asMap() debe devolver los datos representados en un simple objeto Map, ya que se mapeará a la entrada de NetworkTables a la que corresponde. En este caso, podemos representar el punto como sus coordenadas X e Y y devolver un Map que las contenga.

import edu.wpi.first.shuffleboard.api.data.ComplexData;

import java.util.Map;

public final class MyPoint2D extends ComplexData<MyPoint2D> {

   private final double x;
   private final double y;

   // Constructor should take all the different fields needed and assign them to their corresponding instance variables.
   public Point(double x, double y) {
      this.x = x;
      this.y = y;
   }

   @Override
   public Map<String, Object> asMap() {
      return Map.of("x", this.x, "y", this.y);
   }
 }

Otros métodos pueden añadirse para recuperar o editar campos y variables de la instancia, sin embargo, es buena práctica hacer estas clases inmutables para prevenir cambios en la fuente de datos. En cambio, puede realizar una copia del objeto en lugar de manipular el objeto existente. Por ejemplo, si quisiera cambiar la coordenada y de su punto, podría definir el siguiente método:

public MyPoint2D withY(double newY) {
   return new MyPoint2D(this.x, newY);
}

Esto crea un nuevo objeto MyPoint2D y lo regresa con una nueva coordenada Y. Puede hacerse lo mismo para la coordenada X.

Crear un Tipo de Dato

Se pueden crear dos tipos de datos: Tipos de datos simples que solo contienen un campo (por ejemplo, un solo número o un string) y tipos de datos complejos que contienen varios campos (por ejemplo, múltiples strings o números).

Para definir un tipo de dato simple, la clase debe extender la clase SimpleDataType<DataType> con el tipo de dato necesario e implementar el método getDefaultValue(). En este ejemplo, usaremos un double como su tipo de dato simple.

public final class MyDoubleDataType extends SimpleDataType<Double> {

   private static final String NAME = "Double";

   private MyDataType() {
      super(NAME, Double.class);
   }

   @Override
   public Double getDefaultValue() {
      return 0.0;
   }

}

El constructor de la clase es privado para asegurar que una sola instancia del tipo de dato exista

Para definir un tipo de dato complejo, la clase debe extender la clase ComplexDataType y sobrescribir los métodos fromMap() y getDefaultValue(). Se continuará usando como ejemplo la clase MyPoint2D para demostrar cómo se ve una clase de tipos de datos complejos.

public final class PointDataType extends ComplexDataType<MyPoint2D> {

   private static final String NAME = "MyPoint2D";
   public static final PointDataType Instance = new PointDataType();

   private PointDataType() {
      super(NAME, MyPoint2D.class);
   }

   @Override
   public Function<Map<String, Object>, MyPoint2D> fromMap() {
      return map -> {
         return new MyPoint2D((double) map.getOrDefault("x", 0.0), (double) map.getOrDefault("y", 0.0));
      };
   }

   @Override
   public MyPoint2D getDefaultValue() {
      // use default values of 0 for X and Y coordinates
      return new MyPoint2D(0, 0);
   }

}

El código de arriba funciona de la siguiente forma:

El método fromMap() crea un nuevo MyPoint2D utilizando los valores de la entrada de NetworkTables a la que está vinculado. El método getOrDefault devolverá 0.0 si no puede obtener los valores de la entrada. El método getDefaultValue devolverá un nuevo objeto MyPoint2D si no hay ninguna fuente presente.

Exportar un tipo de dato al plugin

Para que Shuffleboard reconozca el tipo de dato, el plugin debe exportarlos al sobrescribir el método getDataTypes. Por ejemplo,

public class MyPlugin extends Plugin {

   @Override
   public List<DataType> getDataTypes() {
      return List.of(PointDataType.Instance);
   }

}