001/* 002 * Copyright (C) Cross The Road Electronics. All rights reserved. 003 * License information can be found in CTRE_LICENSE.txt 004 * For support and suggestions contact support@ctr-electronics.com or file 005 * an issue tracker at https://github.com/CrossTheRoadElec/Phoenix-Releases 006 */ 007package com.ctre.phoenix6.hardware; 008 009import java.util.ArrayList; 010 011import com.ctre.phoenix6.BaseStatusSignal; 012import com.ctre.phoenix6.StatusSignal; 013import com.ctre.phoenix6.hardware.core.CorePigeon2; 014import com.ctre.phoenix6.jni.PlatformJNI; 015import com.ctre.phoenix6.sim.DeviceType; 016import com.ctre.phoenix6.wpiutils.AutoFeedEnable; 017import com.ctre.phoenix6.wpiutils.CallbackHelper; 018 019import edu.wpi.first.hal.HAL; 020import edu.wpi.first.hal.HALValue; 021import edu.wpi.first.hal.SimDevice; 022import edu.wpi.first.hal.SimDouble; 023import edu.wpi.first.hal.HAL.SimPeriodicBeforeCallback; 024import edu.wpi.first.hal.SimDevice.Direction; 025import edu.wpi.first.hal.simulation.SimDeviceDataJNI; 026import edu.wpi.first.hal.simulation.SimValueCallback; 027import edu.wpi.first.math.geometry.Quaternion; 028import edu.wpi.first.math.geometry.Rotation2d; 029import edu.wpi.first.math.geometry.Rotation3d; 030import edu.wpi.first.util.sendable.Sendable; 031import edu.wpi.first.util.sendable.SendableBuilder; 032import edu.wpi.first.util.sendable.SendableRegistry; 033import edu.wpi.first.wpilibj.simulation.CallbackStore; 034import edu.wpi.first.wpilibj.simulation.SimDeviceSim; 035 036public class Pigeon2 extends CorePigeon2 implements Sendable, AutoCloseable { 037 /** 038 * The StatusSignal getters are copies so that calls 039 * to the WPI interface do not update any references 040 */ 041 private final StatusSignal<Double> m_yawGetter = getYaw().clone(); 042 private final StatusSignal<Double> m_yawRateGetter = getAngularVelocityZWorld().clone(); 043 private final StatusSignal<Double> m_quatWGetter = getQuatW().clone(); 044 private final StatusSignal<Double> m_quatXGetter = getQuatX().clone(); 045 private final StatusSignal<Double> m_quatYGetter = getQuatY().clone(); 046 private final StatusSignal<Double> m_quatZGetter = getQuatZ().clone(); 047 048 private static final DeviceType kSimDeviceType = DeviceType.PRO_Pigeon2Type; 049 050 private SimDevice m_simPigeon; 051 private SimDouble m_simSupplyVoltage; 052 private SimDouble m_simYaw; 053 private SimDouble m_simRawYaw; 054 private SimDouble m_simPitch; 055 private SimDouble m_simRoll; 056 057 // callbacks to register 058 private final SimValueCallback m_onValueChangedCallback = new OnValueChangedCallback(); 059 private final Runnable m_onPeriodicCallback = new OnPeriodicCallback(); 060 061 // returned registered callbacks 062 private final ArrayList<CallbackStore> m_simValueChangedCallbacks = new ArrayList<CallbackStore>(); 063 private SimPeriodicBeforeCallback m_simPeriodicBeforeCallback = null; 064 065 /** 066 * Constructs a new Pigeon 2 sensor object. 067 * <p> 068 * Constructs the device using the default CAN bus for the system: 069 * <ul> 070 * <li>"rio" on roboRIO 071 * <li>"can0" on Linux 072 * <li>"*" on Windows 073 * </ul> 074 * 075 * @param deviceId ID of the device, as configured in Phoenix Tuner. 076 */ 077 public Pigeon2(int deviceId) { 078 this(deviceId, ""); 079 } 080 081 /** 082 * Constructs a new Pigeon 2 sensor object. 083 * 084 * @param deviceId ID of the device, as configured in Phoenix Tuner. 085 * @param canbus Name of the CAN bus this device is on. Possible CAN bus 086 * strings are: 087 * <ul> 088 * <li>"rio" for the native roboRIO CAN bus 089 * <li>CANivore name or serial number 090 * <li>SocketCAN interface (non-FRC Linux only) 091 * <li>"*" for any CANivore seen by the program 092 * <li>empty string (default) to select the default for the 093 * system: 094 * <ul> 095 * <li>"rio" on roboRIO 096 * <li>"can0" on Linux 097 * <li>"*" on Windows 098 * </ul> 099 * </ul> 100 */ 101 public Pigeon2(int deviceId, String canbus) { 102 super(deviceId, canbus); 103 104 SendableRegistry.addLW(this, "Pigeon 2 (v6) ", deviceId); 105 m_simPigeon = SimDevice.create("CANGyro:Pigeon 2 (v6)", getDeviceID()); 106 if (m_simPigeon != null) { 107 /* Simulated Pigeon2 LEDs change if it's enabled, so make sure we enable it */ 108 AutoFeedEnable.getInstance().start(); 109 m_simPeriodicBeforeCallback = HAL.registerSimPeriodicBeforeCallback(m_onPeriodicCallback); 110 111 m_simSupplyVoltage = m_simPigeon.createDouble("supplyVoltage", Direction.kInput, 12.0); 112 113 m_simYaw = m_simPigeon.createDouble("yaw", Direction.kOutput, 0); 114 115 m_simRawYaw = m_simPigeon.createDouble("rawYawInput", Direction.kInput, 0); 116 m_simPitch = m_simPigeon.createDouble("pitch", Direction.kInput, 0); 117 m_simRoll = m_simPigeon.createDouble("roll", Direction.kInput, 0); 118 119 final SimDeviceSim sim = new SimDeviceSim("CANGyro:Pigeon 2 (v6)"); 120 m_simValueChangedCallbacks.add(sim.registerValueChangedCallback(m_simSupplyVoltage, m_onValueChangedCallback, true)); 121 m_simValueChangedCallbacks.add(sim.registerValueChangedCallback(m_simRawYaw, m_onValueChangedCallback, true)); 122 m_simValueChangedCallbacks.add(sim.registerValueChangedCallback(m_simPitch, m_onValueChangedCallback, true)); 123 m_simValueChangedCallbacks.add(sim.registerValueChangedCallback(m_simRoll, m_onValueChangedCallback, true)); 124 } 125 } 126 127 // ----- Auto-Closable, from Gyro ----- // 128 @Override 129 public void close() { 130 SendableRegistry.remove(this); 131 if (m_simPeriodicBeforeCallback != null) { 132 m_simPeriodicBeforeCallback.close(); 133 m_simPeriodicBeforeCallback = null; 134 } 135 if (m_simPigeon != null) { 136 m_simPigeon.close(); 137 m_simPigeon = null; 138 } 139 140 for (var callback : m_simValueChangedCallbacks) { 141 callback.close(); 142 } 143 m_simValueChangedCallbacks.clear(); 144 145 AutoFeedEnable.getInstance().stop(); 146 } 147 148 // ----- Callbacks for Sim ----- // 149 private class OnValueChangedCallback implements SimValueCallback { 150 @Override 151 public void callback(String name, int handle, int direction, HALValue value) { 152 String deviceName = SimDeviceDataJNI.getSimDeviceName(SimDeviceDataJNI.getSimValueDeviceHandle(handle)); 153 String physType = deviceName + ":" + name; 154 PlatformJNI.JNI_SimSetPhysicsInput(kSimDeviceType.value, getDeviceID(), 155 physType, CallbackHelper.getRawValue(value)); 156 } 157 } 158 159 private class OnPeriodicCallback implements Runnable { 160 @Override 161 public void run() { 162 double value = 0; 163 int err = 0; 164 165 final int deviceID = getDeviceID(); 166 167 value = PlatformJNI.JNI_SimGetPhysicsValue(kSimDeviceType.value, deviceID, "SupplyVoltage"); 168 err = PlatformJNI.JNI_SimGetLastError(kSimDeviceType.value, deviceID); 169 if (err == 0) { 170 m_simSupplyVoltage.set(value); 171 } 172 value = PlatformJNI.JNI_SimGetPhysicsValue(kSimDeviceType.value, deviceID, "Yaw"); 173 err = PlatformJNI.JNI_SimGetLastError(kSimDeviceType.value, deviceID); 174 if (err == 0) { 175 m_simYaw.set(value); 176 } 177 value = PlatformJNI.JNI_SimGetPhysicsValue(kSimDeviceType.value, deviceID, "RawYaw"); 178 err = PlatformJNI.JNI_SimGetLastError(kSimDeviceType.value, deviceID); 179 if (err == 0) { 180 m_simRawYaw.set(value); 181 } 182 value = PlatformJNI.JNI_SimGetPhysicsValue(kSimDeviceType.value, deviceID, "Pitch"); 183 err = PlatformJNI.JNI_SimGetLastError(kSimDeviceType.value, deviceID); 184 if (err == 0) { 185 m_simPitch.set(value); 186 } 187 value = PlatformJNI.JNI_SimGetPhysicsValue(kSimDeviceType.value, deviceID, "Roll"); 188 err = PlatformJNI.JNI_SimGetLastError(kSimDeviceType.value, deviceID); 189 if (err == 0) { 190 m_simRoll.set(value); 191 } 192 } 193 } 194 195 // ----- WPILib Gyro Interface ----- // 196 //WPILib no longer has a Gyro interface, but these methods are standard in FRC. 197 198 /** 199 * Resets the Pigeon 2 to a heading of zero. 200 * <p> 201 * This can be used if there is significant drift in the gyro, 202 * and it needs to be recalibrated after it has been running. 203 */ 204 public void reset() { 205 setYaw(0); 206 } 207 208 /** 209 * Returns the heading of the robot in degrees. 210 * <p> 211 * The angle increases as the Pigeon 2 turns clockwise when looked 212 * at from the top. This follows the NED axis convention. 213 * <p> 214 * The angle is continuous; that is, it will continue from 360 to 215 * 361 degrees. This allows for algorithms that wouldn't want to 216 * see a discontinuity in the gyro output as it sweeps past from 217 * 360 to 0 on the second time around. 218 * 219 * @return The current heading of the robot in degrees 220 */ 221 public double getAngle() { 222 // Negate since getAngle requires cw+ and Pigeon is ccw+ 223 return -m_yawGetter.refresh().getValue(); 224 } 225 226 /** 227 * Returns the rate of rotation of the Pigeon 2. 228 * <p> 229 * The rate is positive as the Pigeon 2 turns clockwise when looked 230 * at from the top. 231 * 232 * @return The current rate in degrees per second 233 */ 234 public double getRate() { 235 // Negate since getAngle requires cw+ and Pigeon is ccw+ 236 return -m_yawRateGetter.refresh().getValue(); 237 } 238 239 /** 240 * Returns the heading of the robot as a {@link Rotation2d}. 241 * <p> 242 * The angle increases as the Pigeon 2 turns counterclockwise when 243 * looked at from the top. This follows the NWU axis convention. 244 * <p> 245 * The angle is continuous; that is, it will continue from 360 to 246 * 361 degrees. This allows for algorithms that wouldn't want to 247 * see a discontinuity in the gyro output as it sweeps past from 248 * 360 to 0 on the second time around. 249 * 250 * @return The current heading of the robot as a {@link Rotation2d} 251 */ 252 public Rotation2d getRotation2d() { 253 // Rotation2d and Pigeon are both ccw+ 254 return Rotation2d.fromDegrees(m_yawGetter.refresh().getValue()); 255 } 256 257 /** 258 * Returns the orientation of the robot as a {@link Rotation3d} 259 * created from the quaternion signals. 260 * 261 * @return The current orientation of the robot as a {@link Rotation3d} 262 */ 263 public Rotation3d getRotation3d() { 264 BaseStatusSignal.refreshAll(m_quatWGetter, m_quatXGetter, m_quatYGetter, m_quatZGetter); 265 return new Rotation3d(new Quaternion(m_quatWGetter.getValue(), m_quatXGetter.getValue(), m_quatYGetter.getValue(), m_quatZGetter.getValue())); 266 } 267 268 // ----- Sendable ----- // 269 @Override 270 public void initSendable(SendableBuilder builder) { 271 builder.setSmartDashboardType("Gyro"); 272 builder.addDoubleProperty("Value", m_yawGetter.asSupplier()::get, this::setYaw); 273 } 274 275}