Class FakeGoBildaPinpoint

java.lang.Object
com.qualcomm.robotcore.hardware.I2cDeviceSynchDevice<com.qualcomm.robotcore.hardware.I2cDeviceSynchSimple>
com.qualcomm.hardware.gobilda.GoBildaPinpointDriver
org.horizon36596.simloop.fakehardware.FakeGoBildaPinpoint
All Implemented Interfaces:
com.qualcomm.robotcore.hardware.HardwareDevice, com.qualcomm.robotcore.hardware.usb.RobotArmingStateNotifier.Callback, FakeDevice

public class FakeGoBildaPinpoint extends com.qualcomm.hardware.gobilda.GoBildaPinpointDriver implements FakeDevice
Headless fake of the goBILDA Pinpoint odometry computer, for SimLoop users whose robots use one. Horizon (FTC 36596) SimLoop.

The public API is the real driver's, unchanged. This class IS a com.qualcomm.hardware.gobilda.GoBildaPinpointDriver - the class FTC SDK 12.0 ships in its Hardware artifact - and it overrides none of that driver's public methods. What it replaces is the one thing a JVM does not have: the device on the other end of the I2C bus. The driver is built on Firmware, an emulated Pinpoint that answers the driver's register reads and writes the way the device's registers do. So every public method - the unit conversions, the heading normalisation, the status decoding, the bulk-read parsing, the CRC check, the "is this reading corrupt" filter - runs the SDK's own code, and behaves exactly as it does on a robot for the register values the device reports. A reflection test pins that no public method is overridden.

This differs from FakeOctoQuad in the one place it has to. The OctoQuad's SDK type is an interface, so its fake implements the interface; the Pinpoint's is a concrete driver, and re-implementing its ~35 public methods would be re-deriving the SDK's code and hoping to match it. The rest follows FakeOctoQuad: the same sim-side seam (attachChassisAt(double, double, double) and moveChassisTo(double, double, double)), the same on-device integrator, the same fault injection, the same fail-loud rule for anything not modelled.

What the emulated device does

  • The pods are mounted the way the sim says, and read the way the robot configured. Two separate things, as on a real robot. The physical mount - where each pod really sits, which way it really counts, how many ticks per millimetre it really makes - is sim-only, set with setPhysicalPodMount(double, double, com.qualcomm.hardware.gobilda.GoBildaPinpointDriver.EncoderDirection, com.qualcomm.hardware.gobilda.GoBildaPinpointDriver.EncoderDirection, double) (default: goBILDA 4-bar pods on the tracking centre, counting up forward and left). The configured mount is what the robot code wrote with setOffsets, setEncoderDirections and setEncoderResolution. moveChassisTo(double, double, double) turns the chassis's motion into pod counts through the physical mount; the emulated firmware turns those counts back into a pose through the configured one. When the two agree the pose is the chassis's motion; when a robot sets a pod direction, offset or resolution wrong, its pose is wrong the way a real Pinpoint's would be.
  • Pose is an on-device integrator, as on the real board: each step it takes the change in the two pod counts and the IMU's turn, and adds the motion they describe onto whatever pose it holds - the pose last written with setPosition/setPosX/setPosY/setHeading, or zero after resetPosAndIMU(). Frame: X forward, Y left, heading CCW-positive; millimetres and radians in the registers, as the driver expects. The heading register is continuous (not wrapped), which is what lets the driver's getHeading(UnnormalizedAngleUnit) exist; the driver wraps it for getHeading(AngleUnit) itself. Within one step the chassis is taken to turn at a steady rate, so the step is an arc, both when making pod counts and when integrating them.
  • The IMU is ideal except for the yaw scalar, which the device multiplies into every heading change, exactly as it corrects a real IMU - so a scalar other than 1 makes the fake's heading wrong by that factor, as it would a perfect IMU's. PLACEHOLDER: the pod counts are integrated exactly, before they are rounded to the whole numbers the encoder registers report, so the sim adds no count quantisation (a 4-bar pod's is 0.05 mm).
  • Velocity is the pose's change over the last update(double) tick, in the same frame as the pose, mm/s and rad/s.
  • Status is READY unless calibrating or forced (forceFault(com.qualcomm.hardware.gobilda.GoBildaPinpointDriver.DeviceStatus)). recalibrateIMU() and resetPosAndIMU() put it in CALIBRATING for CALIBRATION_SECONDS of sim time. PLACEHOLDER: while calibrating, the pose ignores chassis motion (the device must be still to calibrate; what a real unit does if it is moved then is not documented), but the raw pod counts keep counting, as quadrature counters do. resetPosAndIMU() also zeroes the pose and the velocity. PLACEHOLDER: a new fake is READY at once. A real unit powers up NOT_READY and calibrates first, but a SimLoop scenario starts where robot code starts, long after the robot was switched on.
  • A written pose is not motion. setPosition/setPosX/setPosY/ setHeading move the pose without any velocity, as on the real board, whose velocity comes from the pods and the IMU.
  • Firmware version decides the bulk-read format, as in the driver: versions 1 and 2 answer the fixed 40-byte bulk read, 3 and later the configurable one (setBulkReadScope) and CRC-checked reads (setErrorDetectionType(CRC)). The driver itself refuses what a version cannot do. PLACEHOLDER: firmware 1 and 2 are taken to have no quaternion, pitch or roll registers, so the fake fails loud if one is read. The driver does read them on old firmware when it has not yet learnt the version; what a real v1 or v2 unit answers then is not known.

Getters are not always pure reads here, unlike the other fakes (conventions of FakeDevice). The real driver reads a register over the bus, on every call and on every firmware, whenever its getter's register is not in the driver's bulk-read scope. The default scope holds the ten registers update() refreshes (status, loop time, both encoders, pose, velocity), so getDeviceID, getDeviceVersion, getYawScalar, getXOffset, getYOffset, getQuaternion, getPitch and getRoll always read the bus; on firmware 3 and up, a narrower setBulkReadScope adds every register it leaves out. That is the SDK's own behaviour, so the fake keeps it; busReadCount() shows it.

Determinism (domain R5): no wall clock, no randomness. The device changes only when something writes a register, when moveChassisTo(double, double, double) reports motion, or when update(double) ticks it.

  • Nested Class Summary

    Nested classes/interfaces inherited from class com.qualcomm.hardware.gobilda.GoBildaPinpointDriver

    com.qualcomm.hardware.gobilda.GoBildaPinpointDriver.DeviceStatus, com.qualcomm.hardware.gobilda.GoBildaPinpointDriver.EncoderDirection, com.qualcomm.hardware.gobilda.GoBildaPinpointDriver.ErrorDetectionType, com.qualcomm.hardware.gobilda.GoBildaPinpointDriver.GoBildaOdometryPods, com.qualcomm.hardware.gobilda.GoBildaPinpointDriver.ReadData, com.qualcomm.hardware.gobilda.GoBildaPinpointDriver.Register

    Nested classes/interfaces inherited from interface com.qualcomm.robotcore.hardware.HardwareDevice

    com.qualcomm.robotcore.hardware.HardwareDevice.Manufacturer
  • Field Summary

    Fields
    Modifier and Type
    Field
    Description
    static final double
    How long an IMU calibration keeps the device in CALIBRATING, seconds of sim time.
    static final int
    The firmware version a new fake reports.
    static final float
    The goBILDA 4-bar odometry pod's resolution, ticks per millimetre.
    static final int
    What the DEVICE_ID register reports.
    static final int
    The device's own loop time, microseconds, as its LOOP_TIME register reports it.

    Fields inherited from class com.qualcomm.robotcore.hardware.I2cDeviceSynchDevice

    deviceClient, deviceClientIsOwned, isInitialized
  • Constructor Summary

    Constructors
    Constructor
    Description
    A Pinpoint at the origin, READY, reporting DEFAULT_FIRMWARE_VERSION.
    FakeGoBildaPinpoint(int firmwareVersion)
    A Pinpoint at the origin, READY, reporting the given firmware version.
  • Method Summary

    Modifier and Type
    Method
    Description
    void
    attachChassisAt(double plantXMm, double plantYMm, double plantHeadingRad)
    Tell the device where the sim's chassis is before any motion is reported, without moving the device's pose.
    int
    Returns how many I2C reads the driver has made.
    void
    Stop forcing a status; the device reports READY or CALIBRATING again.
    void
    Make the next bulk read (the one update() makes) arrive corrupt, as a bad I2C transfer does: every data byte 0xFF - a status and encoder counts of -1, and NaN for every pose and velocity float - and, on firmware 3, a CRC byte that does not match.
    protected boolean
    The one driver hook overridden.
    void
    forceFault(com.qualcomm.hardware.gobilda.GoBildaPinpointDriver.DeviceStatus status)
    Make the device report this status, as if it had the fault.
    void
    moveChassisTo(double plantXMm, double plantYMm, double plantHeadingRad)
    The chassis is now here, in the drive plant's frame.
    void
    setPhysicalPodMount(double xPodOffsetMm, double yPodOffsetMm, com.qualcomm.hardware.gobilda.GoBildaPinpointDriver.EncoderDirection xPodDirection, com.qualcomm.hardware.gobilda.GoBildaPinpointDriver.EncoderDirection yPodDirection, double podTicksPerMm)
    Sim only: how the two odometry pods are REALLY mounted on the simulated robot - not what the robot code tells the device (setOffsets, setEncoderDirections, setEncoderResolution), but the truth those settings are supposed to describe.
    void
    update(double deltaTime)
    Advance the device's own clock: finish a calibration that has run its time, and work out the velocity the device reports from the motion since the last tick.

    Methods inherited from class com.qualcomm.hardware.gobilda.GoBildaPinpointDriver

    getDeviceID, getDeviceName, getDeviceStatus, getDeviceVersion, getEncoderX, getEncoderY, getFrequency, getHeading, getHeading, getHeadingVelocity, getLoopTime, getManufacturer, getPitch, getPosition, getPosX, getPosY, getQuaternion, getRoll, getVelX, getVelY, getXOffset, getYawScalar, getYOffset, recalibrateIMU, resetPosAndIMU, setBulkReadScope, setEncoderDirections, setEncoderResolution, setEncoderResolution, setErrorDetectionType, setHeading, setOffsets, setPosition, setPosX, setPosY, setYawScalar, update, update

    Methods inherited from class com.qualcomm.robotcore.hardware.I2cDeviceSynchDevice

    close, disengage, engage, getConnectionInfo, getDeviceClient, getVersion, initialize, initializeIfNecessary, onModuleStateChange, registerArmingStateCallback, resetDeviceConfigurationForOpMode

    Methods inherited from class java.lang.Object

    clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
  • Field Details

    • DEFAULT_FIRMWARE_VERSION

      public static final int DEFAULT_FIRMWARE_VERSION
      The firmware version a new fake reports. SET BY HAND: 3, the first version the SDK 12.0 driver reads with the configurable bulk read and CRC (every later version is read the same way).
      See Also:
    • CALIBRATION_SECONDS

      public static final double CALIBRATION_SECONDS
      How long an IMU calibration keeps the device in CALIBRATING, seconds of sim time. PLACEHOLDER: a quarter second, from the SDK driver's javadoc for recalibrateIMU() and resetPosAndIMU() ("This takes approximately 0.25 seconds"); not measured on a unit.
      See Also:
    • LOOP_TIME_MICROSECONDS

      public static final int LOOP_TIME_MICROSECONDS
      The device's own loop time, microseconds, as its LOOP_TIME register reports it. PLACEHOLDER: 667 us, about 1.5 kHz, inside the range the SDK driver's javadoc for getLoopTime() calls healthy (it says values commonly below 500 or above 1100 mean something is wrong with the device); not measured on a unit. It must be at least 2, because on firmware 1 and 2 the driver's fixed bulk read treats a loop time of 1 or less as a corrupt read (the configurable read of firmware 3 and up does not check it).
      See Also:
    • DEVICE_ID

      public static final int DEVICE_ID
      What the DEVICE_ID register reports. PLACEHOLDER: 1; nothing in the driver checks it.
      See Also:
    • DEFAULT_TICKS_PER_MM

      public static final float DEFAULT_TICKS_PER_MM
      The goBILDA 4-bar odometry pod's resolution, ticks per millimetre. SPEC: the SDK driver's own goBILDA_4_BAR_POD constant (19.89436789 ticks/mm). Used twice: as the resolution the device's register holds before anyone sets one (PLACEHOLDER: the driver does not say what a real unit starts with), and as the resolution of the sim's default physical pods (setPhysicalPodMount(double, double, com.qualcomm.hardware.gobilda.GoBildaPinpointDriver.EncoderDirection, com.qualcomm.hardware.gobilda.GoBildaPinpointDriver.EncoderDirection, double)).
      See Also:
  • Constructor Details

    • FakeGoBildaPinpoint

      public FakeGoBildaPinpoint()
      A Pinpoint at the origin, READY, reporting DEFAULT_FIRMWARE_VERSION.
    • FakeGoBildaPinpoint

      public FakeGoBildaPinpoint(int firmwareVersion)
      A Pinpoint at the origin, READY, reporting the given firmware version.
      Parameters:
      firmwareVersion - the version the DEVICE_VERSION register reports; 1 or 2 selects the fixed bulk read, 3 and up the configurable one; must be positive
  • Method Details

    • doInitialize

      protected boolean doInitialize()
      The one driver hook overridden. The real one casts the bus to the REV hub's LynxI2cDeviceSynch to set its speed to 400 kHz; this bus is not a REV hub and has no speed. Called only by initialize(), which the SDK runs when the hardware is armed.
      Overrides:
      doInitialize in class com.qualcomm.hardware.gobilda.GoBildaPinpointDriver
    • attachChassisAt

      public void attachChassisAt(double plantXMm, double plantYMm, double plantHeadingRad)
      Tell the device where the sim's chassis is before any motion is reported, without moving the device's pose. Call it once, when a drive plant is wired to this device, with that plant's current pose.

      Millimetres, not inches. SimLoop's drive plants report inches, so multiply by 25.4 first: attachChassisAt(plant.getX() * 25.4, plant.getY() * 25.4, plant.getHeading()). Nothing can catch inches passed here; the robot would simply read a pose 25.4 times too small.

      Call it again after any teleport of the plant (setPose on a drive plant, a scenario placing the robot). moveChassisTo(double, double, double) treats every change as driven motion, so a teleport not announced here reaches the pods as one enormous step - a jump in the pose and a velocity spike that the real driver then refuses as a bad read.

      Wiring a robot that configures its Pinpoint also means calling setPhysicalPodMount(double, double, com.qualcomm.hardware.gobilda.GoBildaPinpointDriver.EncoderDirection, com.qualcomm.hardware.gobilda.GoBildaPinpointDriver.EncoderDirection, double) with the same pod offsets, directions and resolution the robot writes. The default physical mount is pods on the centre at 4-bar resolution, counting forward; a robot that writes its real offsets or resolution against that default drifts while it turns, or reads every distance at the wrong scale, exactly as a real robot whose settings do not match its pods would.

      Parameters:
      plantXMm - the chassis X in the drive plant's frame (forward at heading 0), millimetres
      plantYMm - the chassis Y in the drive plant's frame (left at heading 0), millimetres
      plantHeadingRad - the chassis heading in the drive plant's frame, radians CCW
    • moveChassisTo

      public void moveChassisTo(double plantXMm, double plantYMm, double plantHeadingRad)
      The chassis is now here, in the drive plant's frame. The pods roll through the motion since the last call and count it, through the physical mount (setPhysicalPodMount(double, double, com.qualcomm.hardware.gobilda.GoBildaPinpointDriver.EncoderDirection, com.qualcomm.hardware.gobilda.GoBildaPinpointDriver.EncoderDirection, double)); the emulated firmware adds the motion those counts and the IMU describe onto its own pose, as the real board does - so a pose the robot wrote stays written, and the plant and the device do not need to share an origin. While calibrating, the pose ignores the motion; the raw pod counts do not. Millimetres, as attachChassisAt(double, double, double): convert a plant's inches first.

      The heading may be wrapped or not. The turn since the last call is taken the short way round, into (-pi, pi], so a plant whose heading jumps from +pi to -pi has turned a little, not most of a circle; that means one call must never be asked to report a turn of half a circle or more.

      Parameters:
      plantXMm - the chassis X in the drive plant's frame (forward at heading 0), millimetres
      plantYMm - the chassis Y in the drive plant's frame (left at heading 0), millimetres
      plantHeadingRad - the chassis heading in the drive plant's frame, radians CCW, wrapped or not
    • setPhysicalPodMount

      public void setPhysicalPodMount(double xPodOffsetMm, double yPodOffsetMm, com.qualcomm.hardware.gobilda.GoBildaPinpointDriver.EncoderDirection xPodDirection, com.qualcomm.hardware.gobilda.GoBildaPinpointDriver.EncoderDirection yPodDirection, double podTicksPerMm)
      Sim only: how the two odometry pods are REALLY mounted on the simulated robot - not what the robot code tells the device (setOffsets, setEncoderDirections, setEncoderResolution), but the truth those settings are supposed to describe. The pods count the chassis's motion through this mount; the device reads the counts through the configured one. Setting the two differently is how a test shows what a robot with a mis-set Pinpoint would read. It takes effect from the next moveChassisTo(double, double, double); counts already made stay.

      Default: goBILDA 4-bar pods (DEFAULT_TICKS_PER_MM) on the tracking centre, both counting up in the device's positive direction. PLACEHOLDER: a robot's real pods are rarely on the centre; the default only makes a robot that configures nothing read the chassis exactly, because the device's own offset registers start at zero too.

      Parameters:
      xPodOffsetMm - how far LEFT of the tracking centre the X (forward-measuring) pod really is, millimetres, right negative - the same sign convention as setOffsets
      yPodOffsetMm - how far FORWARD of the tracking centre the Y (strafe-measuring) pod really is, millimetres, backward negative - the same sign convention as setOffsets
      xPodDirection - FORWARD if the X pod really counts up as the robot drives forward, REVERSED if it counts down - that is, the setting the robot has to pass to setEncoderDirections to read it correctly
      yPodDirection - FORWARD if the Y pod really counts up as the robot drives left, REVERSED if it counts down
      podTicksPerMm - how many counts both pods really make per millimetre rolled; must be positive
    • update

      public void update(double deltaTime)
      Advance the device's own clock: finish a calibration that has run its time, and work out the velocity the device reports from the motion since the last tick.

      Not the driver's update(), which reads the device; this is the device itself running. A scenario calls this (or FakeHardwareMap.updateAll) once per tick, then the robot code calls update() as it would on the robot. The two share a name because every SimLoop fake's tick is FakeDevice.update(double); a scenario that calls only one of them sees stale readings, exactly as a robot that never calls update() does.

      Specified by:
      update in interface FakeDevice
      Parameters:
      deltaTime - the tick, seconds
    • forceFault

      public void forceFault(com.qualcomm.hardware.gobilda.GoBildaPinpointDriver.DeviceStatus status)
      Make the device report this status, as if it had the fault. It stays until clearFault().

      Not FAULT_BAD_READ: the device never reports that. It is the status the SDK driver sets itself when it catches a corrupt read (a failed CRC, a NaN, an impossible jump), so a test that wants it uses corruptNextBulkRead() and lets the real driver catch it.

      Parameters:
      status - what getDeviceStatus() should decode to after the next read
      Throws:
      IllegalArgumentException - for FAULT_BAD_READ
    • corruptNextBulkRead

      public void corruptNextBulkRead()
      Make the next bulk read (the one update() makes) arrive corrupt, as a bad I2C transfer does: every data byte 0xFF - a status and encoder counts of -1, and NaN for every pose and velocity float - and, on firmware 3, a CRC byte that does not match. The real driver's own checks then decide what happens: with CRC detection it discards the whole read and sets FAULT_BAD_READ. With its default local test and a scope that holds pose or velocity registers (the default scope does, and the fixed read of firmware 1 and 2 always does), it refuses the NaN floats, keeps their last good values, lets the integer registers it does not check through, and sets FAULT_BAD_READ. With the local test and a scope of integer registers only, the driver checks nothing and the garbage gets through with no fault; so it does with detection NONE. PLACEHOLDER: all-0xFF is one kind of corrupt read, chosen because the driver's checks catch it in the default setup; a real bus fault can look like anything.
    • clearFault

      public void clearFault()
      Stop forcing a status; the device reports READY or CALIBRATING again.
    • busReadCount

      public int busReadCount()
      Returns how many I2C reads the driver has made. Real hardware has no such counter; it exists so a test can pin, say, one bulk read per update().
      Returns:
      how many I2C reads the driver has made