Périphérique de simulation

WPILib offre un moyen de gérer les données du périphérique de simulation sous la forme de l’API SimDevice

Simulation des classes fondamentales de l’infrastructure WPILib

Les classes fondamentales de l’infrastructure WPILib (c.-à-d. Encoder, Ultrasonic, etc.) ont des classes de simulation nommées EncoderSim, UltrasonicSim, etc. Ces classes permettent des interactions avec les données du simulateur qui ne seraient pas possibles ou valides en dehors de la simulation. Les construire en dehors de la simulation n’interférera probablement pas avec votre code, mais appeler leurs fonctions et autres est un comportement non défini - dans le meilleur des cas, ils ne feront rien, les cas pires quant à eux pourraient planter votre code! Placez le code de simulation fonctionnelle dans des fonctions de simulation uniquement (telles que simulationPeriodic()) ou enveloppez-les avec RobotBase.isReal()/ RobotBase::IsReal() checks (qui sont constexpr en C++).

Note

Cet exemple utilisera la classe EncoderSim à titre d’exemple. L’utilisation d’autres classes de simulation se fera quasiment de la même manière.

Création d’objets pour le dispositif de simulation

L’objet du dispositif de simulation peut être construit de deux façons :

  • Un constructeur qui accepte l’objet matériel ordinaire.

  • Un constructeur ou une méthode par défaut qui accepte le numéro de port/index/canal à qui l’appareil est connecté. Ce serait le même numéro qui a été utilisé pour construire l’objet matériel régulier. Ceci est particulièrement utile pour unit testing.

// create a real encoder object on DIO 2,3
Encoder encoder = new Encoder(2, 3);
// create a sim controller for the encoder
EncoderSim simEncoder = new EncoderSim(encoder);
// create a real encoder object on DIO 2,3
frc::Encoder encoder{2, 3};
// create a sim controller for the encoder
frc::sim::EncoderSim simEncoder{encoder};

Lecture et écriture des données de simulation

Chaque classe de simulation a des fonctions getter (getXxx()/GetXxx()) et setter (setXxx(value)/SetXxx(value)) pour chaque champ Xxx. Les fonctions getter retourneront la même chose que le getter de la classe régulière pour le robot réel.

simEncoder.setCount(100);
encoder.getCount(); // 100
simEncoder.getCount(); // 100
simEncoder.SetCount(100);
encoder.GetCount(); // 100
simEncoder.GetCount(); // 100

Enregistrement des Callbacks

En plus des getters et des setters, chaque champ dispose également d’une fonction registerXxxCallback() qui enregistre un callback ou rappel à exécuter à chaque fois que la valeur du champ change et renvoie un objet CallbackStore. Les callbacks acceptent un paramètre de type chaîne de caractère correspondant au nom du champ et un objet HALValue contenant la nouvelle valeur. Avant de récupérer les valeurs d’une HALValue, vérifiez le type de valeur contenue. Les types possibles sont HALValue.kBoolean/HAL_BOOL, HALValue.kDouble/HAL_DOUBLE, HALValue.kEnum/HAL_ENUM, HALValue.kInt/HAL_INT, HALValue.kLong/HAL_LONG.

En Java, appelez close() sur l’objet CallbackStore pour annuler le callback. Gardez une référence à l’objet afin qu’il ne soit pas récupéré par le garbage collector - sinon le callback sera annulé par GC. Pour fournir des données arbitraires au callback, capturez-les dans le lambda ou utilisez une référence de méthode.

En C++, enregistrez l’objet CallbackStore dans la bonne portée - le callback sera annulé lorsque l’objet sort de sa portée et est détruit. Les données arbitraires peuvent être transmises aux callbacks via le paramètre param.

Avertissement

Tenter de récupérer une valeur d’un type à partir d’une HALValue contenant un type différent est un comportement non défini.

NotifyCallback callback = (String name, HALValue value) -> {
  if (value.getType() == HALValue.kInt) {
    System.out.println("Value of " + name + " is " + value.getInt());
  }
}
CallbackStore store = simEncoder.registerCountCallback(callback);

store.close(); // cancel the callback
HAL_NotifyCallback callback = [](const char* name, void* param, const HALValue* value) {
  if (value->type == HAL_INT) {
    wpi::outs() << "Value of " << name << " is " << value->data.v_int << '\n';
  }
};
frc::sim::CallbackStore store = simEncoder.RegisterCountCallback(callback);
// the callback will be canceled when ``store`` goes out of scope

Simulation d’autres dispositifs - La classe SimDevicesim

Note

Les fournisseurs peuvent implémenter leur connexion à l’API SimDevice d’une manière légèrement différente de celle décrite ici. Ils peuvent également fournir une classe de simulation spécifique pour leurs classes de composants. Consultez la documentation de votre fournisseur pour plus d’informations sur ce qu’il prend en charge et la manière de le faire.

La classe SimDeviceSim (pas SimDevice!) est un objet de simulation de périphérique général pour les périphériques qui ne sont pas des périphériques WPILib de base et qui n’ont donc pas de classes de simulation spécifiques, telles que les périphériques des fournisseurs. Ces composants apparaîtront dans l’onglet Other Devices du document SimGUI.

L’objet SimDeviceSim est créé à l’aide d’une clé de chaîne identique à la clé utilisée par le fournisseur pour construire le SimDevice sous-jacent dans sa classe du composant. Cette clé est celle avec qui le composant apparaît dans l’onglet Other Devices, et est généralement de la forme Prefix:Device Name[index]. Si la clé contient des ports/index/numéros de canal, ils peuvent être transmis sous forme d’arguments distincts au constructeur SimDeviceSim. La clé contient un préfixe qui est caché par défaut dans le SimGUI, il peut être affiché en sélectionnant l’option Show prefix. Ne pas inclure ce préfixe dans la clé transmise à SimDeviceSim ne se reférera pas au bon composant!

SimDeviceSim device = new SimDeviceSim(deviceKey, index);
frc::sim::SimDeviceSim device{deviceKey, index};

Une fois que nous avons le SimDeviceSim, nous pouvons obtenir des objets SimValue représentant les champs du composant. Il existe également des sous-classes de type spécifique SimDouble, SimInt, SimLong, SimBoolean, et SimEnum qui devraient être utilisées au lieu de la classe non fiable SimValue. Celles-ci sont construites à partir de SimDeviceSim à l’aide d’une clé identique à celle utilisée par le fournisseur pour définir le champ. Cette clé est celle inscrite dans le champ apparaissant telle que dans le SimGUI. Tenter de récupérer un objet SimValue en dehors du simulateur ou lorsque le composant ou les clés de champ n’ont pas d’objet retournera null - cela peut provoquer NullPointerException en Java ou un comportement non défini dans C++.

SimDouble field = device.getDouble(fieldKey);
field.get();
field.set(value);
hal::SimDouble field = device.GetDouble(fieldKey);
field.Get();
field.Set(value);