Création de types de données personnalisés

Les widgets nous permettent de contrôler et de visualiser différents types de données. Ces données peuvent être des nombres entiers, doubles ou même des objets Java. Afin d’afficher ces types de données à l’aide de widgets, il est utile de créer une classe conteneur pour eux. Il n’est pas nécessaire de créer votre propre classe de données si le widget gère des types de données en champ unique tels que des doubles, des tableaux ou des chaînes de caractères..

Création de la classe de données

Dans cet exemple, nous allons créer un type de données personnalisé pour un point 2D et ses coordonnées x et y. Pour créer une classe de type de données personnalisée, elle doit étendre la classe abstraite ComplexData. Votre classe de données personnalisée doit également implémenter la méthode asMap() qui renvoie les données représentées sous la forme d’une carte simple, comme indiqué ci-dessous avec l’annotation @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);
   }
}

Il est également recommandé de remplacer les méthodes par défaut equals et hashcode pour garantir que différents objets sont considérés comme équivalents lorsque leurs champs sont identiques. La méthode asMap() doit renvoyer les données représentées dans un simple objet Map car il sera mappé à l’entrée NetworkTables à laquelle il correspond. Dans ce cas, nous pouvons représenter le point comme ses coordonnées X et Y et renvoyer une Map les contenant.

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);
   }
 }

D’autres méthodes peuvent être ajoutées pour récupérer ou modifier des champs et des variables d’instance, mais il est recommandé de rendre ces classes immuables pour éviter de changer les objets de données source. Au lieu de cela, vous pouvez créer une copie du nouvel objet au lieu de manipuler l’objet existant. Par exemple, si nous voulions changer la coordonnée y de notre point, nous pourions définir la méthode suivante:

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

Cela crée un nouvel objet MyPoint2D et le renvoie avec la nouvelle coordonnée y. La même chose peut être faite pour changer la coordonnée x.

Création d’un type de données

Il existe deux types de données différents: les types de données simples qui n’ont qu’un seul champ (c’est-à-dire un seul nombre ou chaîne de caractères) et les types de données complexes, qui ont plusieurs champs de données (c’est-à-dire plusieurs chaînes, plusieurs nombres).

Afin de définir un type de données simple, la classe doit étendre la classe SimpleDataType<DataType> avec le type de données requis et implémenter la méthode getDefaultValue(). Dans cet exemple, nous utiliserons un double comme type de données 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;
   }

}

Le constructeur de classe est défini comme private pour garantir qu’une seule instance du type de données existera.

Afin de définir un type de données complexe, la classe doit étendre la classe ComplexDataType et remplacer les méthodes fromMap() et getDefaultValue(). Nous utiliserons notre classe MyPoint2D comme exemple pour voir à quoi ressemblerait une classe de type de données complexe.

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);
   }

}

Le code suivant ci-dessus fonctionne comme indiqué:

La méthode fromMap() crée un nouveau MyPoint2D en utilisant les valeurs de l’entrée NetworkTables à laquelle il est lié. La méthode getOrDefault renverra 0.0 si elle ne peut pas obtenir les valeurs d’entrée. Le getDefaultValue renverra un nouvel objet MyPoint2D si aucune source n’est présente.

Exportation du type de données vers le plugin

Afin que le type de données soit reconnu par le Shuffleboard, le plugin doit les exporter en remplaçant la méthode getDataTypes. Par exemple,

public class MyPlugin extends Plugin {

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

}