Class Frame<T>

java.lang.Object
com.reduxrobotics.frames.Frame<T>
Direct Known Subclasses:
ByteArrayFrame, DoubleFrame, LongFrame

public abstract class Frame<T> extends Object
Class representing periodic timestamped data received from CAN or other sources.

For applications like latency compensation, we often need both sensor/device data and a timestamp of when the data was received. Frames provide this by holding the most recently received raw data and the timestamps they were received at and allowing retrieval of both data and timestamps in one FrameData object via getFrameData(), avoiding race conditions involving reading data and timestamp separately.

Additionally, they allow for:

In Java, non-primitive data types cannot be easily passed by value -- instead the language prefers immutable record type objects for compound data structures. However, in an FRC application, this would mean a ton of objects getting thrown to the garbage collector from every time a Frame gets updated rendering the previous value object stale. To get around this, implementing subclasses such as DoubleFrame, LongFrame, and ByteArrayFrame on update copy the new value to a primitive field (or in the case of ByteArrayFrame, an array of primitives). As primitives in Java have value semantics, this avoids instantiation of new objects on every update, and the final object value is only instantiated on a getValue() call via a constructor-supplied raw-to-finished data conversion function. Often, the conversion function is simply the constructor of the final object.

  • Constructor Details

    • Frame

      public Frame(double timestamp)
      Constructs a new Frame object.
      Parameters:
      timestamp - The initial timestamp at which the value was received in seconds.
  • Method Details

    • update

      protected void update(double timestamp)
      Updates the Frame's value, notifying any listeners of new data. Implementing classes should call this function in a synch block in their own update methods.
      Parameters:
      timestamp - the new timestamp of the received data, in seconds
    • addCallback

      public boolean addCallback(Frame.FrameCallback<T> callback)
      Add a callback that will be run whenever this Frame gets updated. Example application:
       // Log Canandcoder position FrameData.
       List<FrameData<Double>> data = new ArrayList<>();
       Canandcoder enc0 = new Canandcoder(0);
       
       enc0.getPositionFrame().addCallback(frameData -> {
           data.add(frameData);
       });
       // Timestamped data is now streamed into the List.
       
       
      Parameters:
      callback - the callback function, taking a FrameData
      Returns:
      true on success, false if the callback has already been added.
    • removeCallback

      public boolean removeCallback(Frame.FrameCallback<T> callback)
      Remove a registered callback by the handle.
      Parameters:
      callback - the callback function, taking a FrameData
      Returns:
      true on success, false if the callback has already been added.
    • getFrameData

      public FrameData<T> getFrameData()
      Returns an immutable FrameData<T> class containing both value and timestamp. This bypasses the race condition possible via calling getValue() followed by getTimestamp() individually.
      Returns:
      frame data
    • getValue

      public abstract T getValue()
      Returns the value of the data frame.
      Returns:
      the value the data frame holds.
    • getTimestamp

      public double getTimestamp()
      Gets the timestamp in seconds of when this value was updated. The time base is relative to the FPGA timestamp.
      Returns:
      the timestamp in seconds.
    • waitForFrames

      public static FrameData<?>[] waitForFrames(double timeout, Frame<?>... frames)
      Waits for all Frames to have transmitted a value. Either returns an array of FrameData representing the data from corresponding frames passed in (in the order they are passed in) or null if timeout or interrupt is hit.
       // Keep in mind this code sample will likely cause timing overruns 
       // if on the main thread of your robot code.
       
       // some definitions for reference:
       Canandcoder enc = new Canandcoder(0);
       Canandcoder enc1 = new Canandcoder(1);
       
       // in your side thread function:
       
       // wait for up to 40 ms for position and velocity to come in from two Canandcoders
       FrameData<?>[] data = Frame.waitForFrames(0.040, enc.getPositionFrame(), enc.getVelocityFrame(), enc1.getPositionFrame());
       if (data == null) {
         System.out.printf("waitForFrames timed out before receiving all data\n");
       } else {
         // blind casts are needed to pull the data out of the array
         FrameData<Double> posFrame = (FrameData<Double>) data[0];
         FrameData<Double> velFrame = (FrameData<Double>) data[1];
         FrameData<Double> posFram1 = (FrameData<Double>) data[2];
      
         // fetches the maximum timestamp across all received timestamps (the "latest" value)
         double latest = Frame.maxTimestamp(data);
      
         // prints the received frame value and how far behind the latest received CAN timestamp it was
         System.out.printf("posFrame: %.3f, %.3f ms\n", posFrame.getValue(), (latest - posFrame.getTimestamp()) * 1000);
         System.out.printf("velFrame: %.3f, %.3f ms\n", velFrame.getValue(), (latest - velFrame.getTimestamp()) * 1000);
         System.out.printf("posFram1: %.3f, %.3f ms\n", posFram1.getValue(), (latest - posFram1.getTimestamp()) * 1000);
       } 
       
      Parameters:
      timeout - maximum seconds to wait for before giving up
      frames - Frame handles to wait on. Position in argument list corresponds to position in the returned FrameData array.
      Returns:
      an array of FrameData; representing the data from corresponding frames passed in or null if timeout or interrupt is hit.
    • maxTimestamp

      public static double maxTimestamp(FrameData<?>[] data)
      Returns the max timestamp from a tuple of FrameData objects. Most useful for getting the "latest" CAN timestamp from a result of waitForFrames(double, com.reduxrobotics.frames.Frame<?>...).
      Parameters:
      data - frame data array from waitForFrames(double, com.reduxrobotics.frames.Frame<?>...)
      Returns:
      the maximum timestamp