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