Simulación de Dispositivos

WPILib proporciona una forma de gestionar los datos de los dispositivos de simulación en forma de la API SimDevice.

Simulación de las clases de dispositivos del núcleo WPILib

Las clases principales de dispositivos WPILib (por ejemplo, Encoder, Ultrasonic, etc.) tienen clases de simulación llamadas EncoderSim, UltrasonicSim, etc. Estas clases permiten interacciones con los datos del dispositivo que no serían posibles o válidas fuera de la simulación. Construirlas fuera de la simulación probablemente no interferirá con tu código, pero llamar a sus funciones y similares es un comportamiento indefinido - en el mejor de los casos no harán nada, ¡en los peores casos podrían bloquear su código! Coloque el código de simulación funcional en funciones sólo de simulación (como simulationPeriodic()) o envuélvalas con comprobaciones RobotBase.isReal()/ RobotBase::IsReal() (que son constexpr en C++).

Nota

Este ejemplo utilizará la clase «EncoderSim» como ejemplo. El uso de otras clases de simulación será casi idéntico.

Creación de objetos de dispositivos de simulación

El objeto de dispositivo de simulación puede construirse de dos maneras:

  • un constructor que acepta el objeto hardware normal.

  • un constructor o método de fábrica que acepte el número de puerto/índice/canal al que está conectado el dispositivo. Este sería el mismo número que se utilizó para construir el objeto de hardware regular. Esto es especialmente útil para las :doc:` pruebas unitarias<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};

Lectura y escritura de datos del dispositivo

Cada clase de simulación tiene funciones getter (getXxx()/GetXxx()`) y setter (setXxx(valor)/SetXxx(valor)`) para cada campo Xxx. Las funciones getter devolverán lo mismo que el getter de la clase de dispositivo normal.

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

Registro de devoluciones de llamada

Además de los getters y setters, cada campo tiene una función registerXxxCallback() que registra una llamada de retorno que se ejecutará cada vez que el valor del campo cambie y devuelve un objeto CallbackStore. Las llamadas de retorno aceptan un parámetro de cadena con el nombre del campo y un objeto HALValue que contiene el nuevo valor. Antes de recuperar valores de un HALValue, comprueba el tipo de valor que contiene. Los tipos posibles son HALValue.kBoolean/HAL_BOOL`, HALValue.kDouble/HAL_DOUBLE`, HALValue.kEnum/HAL_ENUM`, HALValue.kInt/HAL_INT`, HALValue.kLong/HAL_LONG`.

En Java, llama a close() en el objeto CallbackStore para cancelar la llamada de retorno. Mantén una referencia al objeto para que no sea recolectado por la basura - de lo contrario la devolución de llamada será cancelada por la GC. Para proporcionar datos arbitrarios a la llamada de retorno, captúralos en la lambda o utiliza una referencia a un método.

En C++, guarda el objeto CallbackStore en el ámbito correcto - la llamada de retorno se cancelará cuando el objeto salga del ámbito y se destruya. Se pueden pasar datos arbitrarios a las llamadas de retorno a través del parámetro param.

Advertencia

Intentar recuperar un valor de un tipo desde un HALValue que contiene un tipo diferente es un comportamiento indefinido.

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

Simulación de otros dispositivos - La clase SimDeviceSim

Nota

Los proveedores pueden implementar su conexión a la API SimDevice de forma ligeramente diferente a la descrita aquí. También pueden proporcionar una clase de simulación específica para su clase de dispositivo. Consulte la documentación de su proveedor para obtener más información sobre lo que soportan y cómo.

The SimDeviceSim (not SimDevice!) class is a general device simulation object for devices that aren’t core WPILib devices and therefore don’t have specific simulation classes - such as vendor devices. These devices will show up in the Other Devices tab of the SimGUI.

El objeto SimDeviceSim se crea utilizando una clave de cadena idéntica a la que el proveedor utilizó para construir el SimDevice subyacente en su clase de dispositivo. Esta clave es la que muestra el dispositivo en la pestaña Otros dispositivos, y suele tener la forma Prefijo:Nombre del dispositivo[índice]. Si la clave contiene números de puertos/índices/canales, pueden pasarse como argumentos separados al constructor de SimDeviceSim. La clave contiene un prefijo que está oculto por defecto en la SimGUI, puede mostrarse seleccionando la opción Show prefix. Si no se incluye este prefijo en la clave que se pasa a SimDeviceSim, ¡no coincidirá con el dispositivo!

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

Una vez que tenemos el SimDeviceSim, podemos obtener objetos SimValue que representan los campos del dispositivo. También existen subclases específicas de tipo SimDouble, SimInt, SimLong, SimBoolean y SimEnum, que deben utilizarse en lugar de la clase SimValue, que no es segura. Se construyen a partir de SimDeviceSim utilizando una clave de cadena idéntica a la que el proveedor utilizó para definir el campo. Esta clave es la que aparece en la SimGUI. Si se intenta recuperar un objeto SimValue fuera de la simulación o cuando las claves del dispositivo o del campo no coinciden, se devolverá null - esto puede causar una NullPointerException en Java o un comportamiento indefinido en C++.

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