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}