Hoot Replay =========== .. note:: Information on logging to ``hoot`` files can be found in :doc:`/docs/api-reference/api-usage/signal-logging`. Hoot Replay allows users to playback hoot logs in their robot program. This allows for testing changes to robot code in simulation using measurements recorded from the real robot. .. important:: Hoot Replay requires the hoot log to have a Pro-licensed device. Currently, only one hoot log may be replayed at a time. Compared to other replay frameworks, Hoot Replay offers **automatic** status signal playback without any code changes. However, replaying custom logged signals still requires modifications to the robot's subsystems. Hoot Replay is controlled using the ``HootReplay`` class (`Java `__, `C++ `__, `Python `__) and supports playing back device status signals and custom user signals. Configs and control requests are ignored during replay. Hoot Replay uses a different vendordep (see :doc:`Installing Phoenix 6 `) that replaces :doc:`/docs/canivore/canivore-hardware-attached` with Hoot Replay. Note that only one Phoenix 6 vendordep may be used in the ``vendordeps`` folder at a time. Starting Hoot Replay -------------------- At the start of the robot program, the desired hoot log can be loaded using ``HootReplay.loadFile(fileName)`` (`Java `__, `C++ `__, `Python `__). Hoot Replay will start automatically after the log has been loaded. .. tab-set:: .. tab-item:: Java :sync: java .. code-block:: java HootReplay.loadFile("./logs/example.hoot"); .. tab-item:: C++ :sync: cpp .. code-block:: cpp HootReplay::LoadFile{"./logs/example.hoot"}; .. tab-item:: Python :sync: python .. code-block:: python HootReplay.load_file("./logs/example.hoot") It is important that the hoot log is loaded before any devices are constructed or configured. To make this process easier, the ``CANBus(canbus, fileName)`` constructor (`Java `__, `C++ `__, `Python `__) can instead be used to load the hoot log. .. tab-set:: .. tab-item:: Java :sync: java .. code-block:: java // construct the CANBus with the hoot log final CANBus canbus = new CANBus("canivore", "./logs/example.hoot"); // now the log is guaranteed to be loaded before devices are constructed final TalonFX talonFX = new TalonFX(0, canbus); .. tab-item:: C++ :sync: cpp .. code-block:: cpp // construct the CANBus with the hoot log CANBus canbus{"canivore", "./logs/example.hoot"}; // now the log is guaranteed to be loaded before devices are constructed hardware::TalonFX talonFX{0, canbus}; .. tab-item:: Python :sync: python .. code-block:: python # construct the CANBus with the hoot log self._canbus = CANBus("canivore", "./logs/example.hoot") # now the log is guaranteed to be loaded before devices are constructed self._talon_fx = hardware.TalonFX(0, canbus) Controlling Replay ------------------ During Hoot Replay, the simulated robot will automatically enable and run through all the maneuvers recorded in the hoot log. There are also several APIs that can be used to manage playback. - ``HootReplay.pause()`` temporarily pauses playback - ``HootReplay.play()`` resumes playback from the current point in the file - ``HootReplay.stop()`` pauses playback and returns to the start of the file - ``HootReplay.restart()`` restarts playback from the start of the file - ``HootReplay.setSpeed(speed)`` changes the speed of playback by a scalar The status of Hoot Replay can be checked using ``HootReplay.isFileLoaded()``, ``HootReplay.isPlaying()``, and ``HootReplay.isFinished()``. Threads can also wait for Hoot Replay to start using ``HootReplay.waitForPlaying(timeout)``. .. tip:: ``HootReplay.isPlaying()`` and ``HootReplay.waitForPlaying(timeout)`` immediately return true when running on the robot or in regular simulation. Additionally, after pausing or stopping Hoot Replay, playback can advance by a fixed timestep using ``HootReplay.stepTiming(timeDelta)``, updating all signals accordingly. .. tab-set:: .. tab-item:: Java :sync: java .. code-block:: java // stop and return to start of log HootReplay.stop(); var startPos = talonFX.getPosition().getValue(); // advance by 1 second and compare positions HootReplay.stepTiming(1.0); var endPos = talonFX.getPosition().getValue(); .. tab-item:: C++ :sync: cpp .. code-block:: cpp // stop and return to start of log HootReplay::Stop(); auto const startPos = talonFX.GetPosition().GetValue(); // advance by 1 second and compare positions HootReplay::StepTiming(1_s); auto const endPos = talonFX.GetPosition().GetValue(); .. tab-item:: Python :sync: python .. code-block:: python # stop and return to start of log HootReplay.stop() start_pos = self._talon_fx.get_position().value # advance by 1 second and compare positions HootReplay.step_timing(1.0) end_pos = self._talon_fx.get_position().value Replaying Custom Signals ------------------------ Users can also fetch custom signals written to the loaded hoot log by utilizing the ``get*()`` functions. An example application of this is replaying vision data to test changes in the drivetrain pose estimator. All custom signal getters return a ``StatusSignal.SignalMeasurement`` (`Java `__, `C++ `__, `Python `__) object containing information about the signal, including its timestamp and any logged units. The success of fetching the custom signal can be validated by checking the ``status`` field. .. tab-set:: .. tab-item:: Java :sync: java .. code-block:: java // Fetch the logged raw vision measurements var visionData = HootReplay.getStruct("camera pose", Pose2d.struct); if (visionData.status.isOK() && visionData.value.isPresent()) { // now run regular vision processing on the vision data var cameraPose = visionData.value.get(); if (isCameraPoseValid(cameraPose)) { drivetrain.addVisionMeasurement(cameraPose, visionData.timestamp); } } .. tab-item:: C++ :sync: cpp .. code-block:: cpp // Fetch the logged raw vision measurements auto const visionData = HootReplay::GetStruct("camera pose"); if (visionData.status.IsOK() && visionData.value.has_value()) { // now run regular vision processing on the vision data auto const cameraPose = visionData.value.value(); if (IsCameraPoseValid(cameraPose)) { drivetrain.AddVisionMeasurement(cameraPose, visionData.timestamp); } } .. tab-item:: Python :sync: python .. code-block:: python # Fetch the logged raw vision measurements vision_data = HootReplay.get_struct("camera pose", Pose2d) if visionData.status.is_ok() and vision_data.value is not None: # now run regular vision processing on the vision data camera_pose = vision_data.value if is_camera_pose_valid(camera_pose): drivetrain.add_vision_measurement(camera_pose, vision_data.timestamp) Additionally, the Signal Logger is supported in Hoot Replay with the following behavior: - Signal Logger is always enabled during replay. As a result, ``SignalLogger::Start()`` and ``SignalLogger::Stop()`` are ignored. - The replayed log will always be written to a ``replay__