001/* 002 * Copyright (c) 2018-2021 REV Robotics 003 * 004 * Redistribution and use in source and binary forms, with or without 005 * modification, are permitted provided that the following conditions are met: 006 * 007 * 1. Redistributions of source code must retain the above copyright notice, 008 * this list of conditions and the following disclaimer. 009 * 2. Redistributions in binary form must reproduce the above copyright 010 * notice, this list of conditions and the following disclaimer in the 011 * documentation and/or other materials provided with the distribution. 012 * 3. Neither the name of REV Robotics nor the names of its 013 * contributors may be used to endorse or promote products derived from 014 * this software without specific prior written permission. 015 * 016 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 017 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 018 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 019 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 020 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 021 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 022 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 023 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 024 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 025 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 026 * POSSIBILITY OF SUCH DAMAGE. 027 */ 028 029package com.revrobotics; 030 031import com.revrobotics.jni.CANSparkMaxJNI; 032import edu.wpi.first.wpilibj.SpeedController; 033import edu.wpi.first.wpilibj.motorcontrol.MotorController; 034import java.nio.ByteBuffer; 035import java.util.concurrent.atomic.AtomicBoolean; 036 037public abstract class CANSparkMaxLowLevel 038 implements SpeedController, MotorController, AutoCloseable { 039 // TODO(Noah): Deprecate and introduce correctly named constants in a more appropriate spot 040 public static final int kAPIMajorVersion = CANSparkMaxJNI.c_SparkMax_GetAPIMajorRevision(); 041 public static final int kAPIMinorVersion = CANSparkMaxJNI.c_SparkMax_GetAPIMinorRevision(); 042 public static final int kAPIBuildVersion = CANSparkMaxJNI.c_SparkMax_GetAPIBuildRevision(); 043 public static final int kAPIVersion = CANSparkMaxJNI.c_SparkMax_GetAPIVersion(); 044 045 public enum MotorType { 046 kBrushed(0), 047 kBrushless(1); 048 049 @SuppressWarnings("MemberName") 050 public final int value; 051 052 MotorType(int value) { 053 this.value = value; 054 } 055 056 public static MotorType fromId(int id) { 057 for (MotorType type : values()) { 058 if (type.value == id) { 059 return type; 060 } 061 } 062 return null; 063 } 064 } 065 066 public enum PeriodicFrame { 067 kStatus0(0), 068 kStatus1(1), 069 kStatus2(2), 070 kStatus3(3); 071 072 @SuppressWarnings("MemberName") 073 public final int value; 074 075 PeriodicFrame(int value) { 076 this.value = value; 077 } 078 079 public static PeriodicFrame fromId(int id) { 080 for (PeriodicFrame type : values()) { 081 if (type.value == id) { 082 return type; 083 } 084 } 085 return null; 086 } 087 } 088 089 public class PeriodicStatus0 { 090 public double appliedOutput; 091 public short faults; 092 public short stickyFaults; 093 public byte lock; 094 public MotorType motorType; 095 public boolean isFollower; 096 public boolean isInverted; 097 public boolean roboRIO; 098 } 099 100 public class PeriodicStatus1 { 101 public double sensorVelocity; 102 public byte motorTemperature; 103 public double busVoltage; 104 public double outputCurrent; 105 } 106 107 public class PeriodicStatus2 { 108 public double sensorPosition; 109 public double iAccum; 110 } 111 112 protected final long sparkMaxHandle; 113 protected final AtomicBoolean isClosed = new AtomicBoolean(false); 114 private final int deviceId; 115 private String firmwareString; 116 protected final MotorType motorType; 117 118 /** 119 * Create a new SPARK MAX Controller 120 * 121 * <p>Package-private, so that only other classes in our package can create 122 * 123 * @param deviceId The device ID 124 * @param type The motor type connected to the controller. Brushless motors must be connected to 125 * their matching color and the hall sensor plugged in. Brushed motors must be connected to 126 * the Red and Black terminals only. 127 */ 128 CANSparkMaxLowLevel(int deviceId, MotorType type) { 129 if (type == null) { 130 throw new IllegalArgumentException("type must not be null"); 131 } 132 this.deviceId = deviceId; 133 firmwareString = ""; 134 motorType = type; 135 if (CANSparkMaxJNI.c_SparkMax_RegisterId(deviceId) == REVLibError.kDuplicateCANId.value) { 136 throw new IllegalStateException( 137 "A CANSparkMax instance has already been created with this device ID: " + deviceId); 138 } 139 sparkMaxHandle = CANSparkMaxJNI.c_SparkMax_Create(deviceId, type.value); 140 } 141 142 /** Closes the SPARK MAX Controller */ 143 @Override 144 public void close() { 145 boolean alreadyClosed = isClosed.getAndSet(true); 146 if (alreadyClosed) { 147 return; 148 } 149 CANSparkMaxJNI.c_SparkMax_Destroy(sparkMaxHandle); 150 } 151 152 /** 153 * Get the firmware version of the SPARK MAX. 154 * 155 * @return uint32_t Firmware version integer. Value is represented as 4 bytes, Major.Minor.Build 156 * H.Build L 157 */ 158 // TODO(Noah): Add a function that returns the different version fields as an object 159 public int getFirmwareVersion() { 160 throwIfClosed(); 161 return CANSparkMaxJNI.c_SparkMax_GetFirmwareVersion(sparkMaxHandle); 162 } 163 164 /** 165 * Set the control frame send period for the native CAN Send thread. 166 * 167 * @param periodMs The send period in milliseconds between 1ms and 100ms or set to 0 to disable 168 * periodic sends. Note this is not updated until the next call to Set() or SetReference(). 169 */ 170 public void setControlFramePeriodMs(int periodMs) { 171 throwIfClosed(); 172 CANSparkMaxJNI.c_SparkMax_SetControlFramePeriod(sparkMaxHandle, periodMs); 173 } 174 175 /** 176 * Get the firmware version of the SPARK MAX as a string. 177 * 178 * @return std::string Human readable firmware version string 179 */ 180 public String getFirmwareString() { 181 throwIfClosed(); 182 if (firmwareString == "") { 183 int version = getFirmwareVersion(); 184 ByteBuffer b = ByteBuffer.allocate(4); 185 b.putInt(version); 186 187 byte[] verBytes = b.array(); 188 189 StringBuilder firmwareString = new StringBuilder(); 190 firmwareString 191 .append("v") 192 .append((int) verBytes[0]) 193 .append(".") 194 .append((int) verBytes[1]) 195 .append(".") 196 .append((int) verBytes[2] << 8 | (int) verBytes[3]); 197 198 this.firmwareString = firmwareString.toString(); 199 } 200 return firmwareString; 201 } 202 203 /** 204 * Get the unique serial number of the SPARK MAX. Not currently available. 205 * 206 * @return byte[] Vector of bytes representig the unique serial number 207 */ 208 public byte[] getSerialNumber() { 209 throwIfClosed(); 210 return new byte[0]; 211 } 212 213 /** 214 * Get the configured Device ID of the SPARK MAX. 215 * 216 * @return int device ID 217 */ 218 public int getDeviceId() { 219 throwIfClosed(); 220 return deviceId; 221 } 222 223 /** 224 * Get the motor type setting for the SPARK MAX 225 * 226 * @return MotorType Motor type setting 227 * @deprecated Use {@link #getMotorType()} instead 228 */ 229 @Deprecated(forRemoval = true) 230 public MotorType getInitialMotorType() { 231 throwIfClosed(); 232 return motorType; 233 } 234 235 /** 236 * Get the motor type setting for the SPARK MAX 237 * 238 * @return MotorType Motor type setting 239 */ 240 public MotorType getMotorType() { 241 throwIfClosed(); 242 return motorType; 243 } 244 245 /** 246 * Set the rate of transmission for periodic frames from the SPARK MAX 247 * 248 * <p>Each motor controller sends back three status frames with different data at set rates. Use 249 * this function to change the default rates. 250 * 251 * <p>Defaults: Status0 - 10ms Status1 - 20ms Status2 - 50ms 252 * 253 * <p>This value is not stored in the FLASH after calling burnFlash() and is reset on powerup. 254 * 255 * <p>Refer to the SPARK MAX reference manual on details for how and when to configure this 256 * parameter. 257 * 258 * @param frameID The frame ID can be one of PeriodicFrame type 259 * @param periodMs The rate the controller sends the frame to the controller. 260 * @return {@link REVLibError#kOk} if successful 261 */ 262 public REVLibError setPeriodicFramePeriod(PeriodicFrame frameID, int periodMs) { 263 throwIfClosed(); 264 return REVLibError.fromInt( 265 CANSparkMaxJNI.c_SparkMax_SetPeriodicFramePeriod(sparkMaxHandle, frameID.value, periodMs)); 266 } 267 268 /** 269 * Allow external controllers to recieve control commands over USB. For example, a configuration 270 * where the heartbeat (and enable/disable) is sent by the main controller, but control frames are 271 * sent by other CAN devices over USB. 272 * 273 * <p>This is global for all controllers on the same bus. 274 * 275 * <p>This does not disable sending control frames from this device. To prevent conflicts, do not 276 * enable this feature and also send Set() for SetReference() from the controllers you wish to 277 * control. 278 * 279 * @param enable Enable or disable external control 280 */ 281 public static void enableExternalUSBControl(boolean enable) { 282 CANSparkMaxJNI.c_SparkMax_EnableExternalControl(enable); 283 } 284 285 /** 286 * Send enabled or disabled command to controllers. This is global for all controllers on the same 287 * bus, and will only work for non-roboRIO targets in non-competiton use. This function will also 288 * not work if a roboRIO is present on the CAN bus. 289 * 290 * <p>This does not disable sending control frames from this device. To prevent conflicts, do not 291 * enable this feature and also send Set() for SetReference() from the controllers you wish to 292 * control. 293 * 294 * @param enable Enable or disable external control 295 */ 296 static void setEnable(boolean enable) { 297 CANSparkMaxJNI.c_SparkMax_SetEnable(enable); 298 } 299 300 REVLibError setFollow(FollowConfig follower) { 301 throwIfClosed(); 302 return REVLibError.fromInt( 303 CANSparkMaxJNI.c_SparkMax_SetFollow( 304 sparkMaxHandle, follower.leaderArbId, follower.config.getRaw())); 305 } 306 307 REVLibError setpointCommand(double value) { 308 throwIfClosed(); 309 return setpointCommand(value, CANSparkMax.ControlType.kDutyCycle); 310 } 311 312 REVLibError setpointCommand(double value, CANSparkMax.ControlType ctrl) { 313 throwIfClosed(); 314 return setpointCommand(value, ctrl, 0); 315 } 316 317 /** @deprecated Use {@link #setpointCommand(double, CANSparkMax.ControlType)} instead. */ 318 @Deprecated(forRemoval = true) 319 @SuppressWarnings("removal") 320 REVLibError setpointCommand(double value, ControlType ctrl) { 321 throwIfClosed(); 322 return setpointCommand(value, ctrl, 0); 323 } 324 325 REVLibError setpointCommand(double value, CANSparkMax.ControlType ctrl, int pidSlot) { 326 throwIfClosed(); 327 return setpointCommand(value, ctrl, pidSlot, 0); 328 } 329 330 /** @deprecated Use {@link #setpointCommand(double, CANSparkMax.ControlType, int)} instead. */ 331 @Deprecated(forRemoval = true) 332 @SuppressWarnings("removal") 333 REVLibError setpointCommand(double value, ControlType ctrl, int pidSlot) { 334 throwIfClosed(); 335 return setpointCommand(value, ctrl, pidSlot, 0); 336 } 337 338 REVLibError setpointCommand( 339 double value, CANSparkMax.ControlType ctrl, int pidSlot, double arbFeedforward) { 340 throwIfClosed(); 341 return setpointCommand(value, ctrl, pidSlot, arbFeedforward, 0); 342 } 343 344 /** 345 * @deprecated Use {@link #setpointCommand(double, CANSparkMax.ControlType, int, double)} instead. 346 */ 347 @Deprecated(forRemoval = true) 348 @SuppressWarnings("removal") 349 REVLibError setpointCommand(double value, ControlType ctrl, int pidSlot, double arbFeedforward) { 350 throwIfClosed(); 351 return setpointCommand(value, ctrl, pidSlot, arbFeedforward, 0); 352 } 353 354 // TODO(Noah): Deprecate all setpointCommand() methods in favor of the corresponding methods in 355 // SparkMaxPIDController. 356 // Next year, we can set them all to be protected. 357 REVLibError setpointCommand( 358 double value, 359 CANSparkMax.ControlType ctrl, 360 int pidSlot, 361 double arbFeedforward, 362 int arbFFUnits) { 363 throwIfClosed(); 364 return REVLibError.fromInt( 365 CANSparkMaxJNI.c_SparkMax_SetpointCommand( 366 sparkMaxHandle, 367 (float) value, 368 ctrl.value, 369 pidSlot, 370 (float) arbFeedforward, 371 arbFFUnits)); 372 } 373 374 /** 375 * @deprecated Use {@link #setpointCommand(double, CANSparkMax.ControlType, int, double, int)} 376 * instead. 377 */ 378 @Deprecated(forRemoval = true) 379 @SuppressWarnings("removal") 380 REVLibError setpointCommand( 381 double value, ControlType ctrl, int pidSlot, double arbFeedforward, int arbFFUnits) { 382 throwIfClosed(); 383 384 return REVLibError.fromInt( 385 CANSparkMaxJNI.c_SparkMax_SetpointCommand( 386 sparkMaxHandle, 387 (float) value, 388 ctrl.value, 389 pidSlot, 390 (float) arbFeedforward, 391 arbFFUnits)); 392 } 393 394 public float getSafeFloat(float f) { 395 throwIfClosed(); 396 if (Float.isNaN(f) || Float.isInfinite(f)) return 0; 397 398 return f; 399 } 400 401 /** 402 * Restore motor controller parameters to factory default until the next controller reboot 403 * 404 * @return {@link REVLibError#kOk} if successful 405 */ 406 public REVLibError restoreFactoryDefaults() { 407 throwIfClosed(); 408 return restoreFactoryDefaults(false); 409 } 410 411 /** 412 * Restore motor controller parameters to factory default 413 * 414 * @param persist If true, burn the flash with the factory default parameters 415 * @return {@link REVLibError#kOk} if successful 416 */ 417 public REVLibError restoreFactoryDefaults(boolean persist) { 418 throwIfClosed(); 419 return REVLibError.fromInt( 420 CANSparkMaxJNI.c_SparkMax_RestoreFactoryDefaults(sparkMaxHandle, persist)); 421 } 422 423 protected void throwIfClosed() { 424 if (isClosed.get()) { 425 throw new IllegalStateException("This SPARK MAX object has previously been closed."); 426 } 427 } 428 429 protected static class FollowConfig { 430 int leaderArbId; 431 Config config; 432 433 public static class Config { 434 public int rsvd1; 435 public int invert; 436 public int rsvd2; 437 public int predefined; 438 439 public int getRaw() { 440 return (rsvd1 & 0x3FFFF) 441 | (invert & 0x1) << 18 442 | (rsvd2 & 0x1F) << 19 443 | (predefined & 0xFF) << 24; 444 } 445 } 446 447 FollowConfig() { 448 config = new Config(); 449 } 450 } 451 452 // package-private to the revrobotics package 453 enum FeedbackSensorType { 454 kNoSensor(0), 455 kHallSensor(1), 456 kQuadrature(2), 457 kSensorless(3), 458 kAnalog(4), 459 kAlternateQuadrature(5); 460 461 @SuppressWarnings("MemberName") 462 public final int value; 463 464 FeedbackSensorType(int value) { 465 this.value = value; 466 } 467 } 468}