001// Copyright (c) FIRST and other WPILib contributors.
002// Open Source Software; you can modify and/or share it under the terms of
003// the WPILib BSD license file in the root directory of this project.
004
005package org.wpilib.hardware.expansionhub;
006
007import static org.wpilib.units.Units.Amps;
008import static org.wpilib.units.Units.Volts;
009import static org.wpilib.util.ErrorMessages.requireNonNullParam;
010
011import org.wpilib.hardware.hal.util.AllocationException;
012import org.wpilib.networktables.BooleanPublisher;
013import org.wpilib.networktables.DoublePublisher;
014import org.wpilib.networktables.DoubleSubscriber;
015import org.wpilib.networktables.IntegerPublisher;
016import org.wpilib.networktables.NetworkTableInstance;
017import org.wpilib.networktables.PubSubOption;
018import org.wpilib.system.SystemServer;
019import org.wpilib.units.measure.Current;
020import org.wpilib.units.measure.Voltage;
021
022/** This class controls a specific motor and encoder hooked up to an ExpansionHub. */
023public class ExpansionHubMotor implements AutoCloseable {
024  private static final int kPercentageMode = 0;
025  private static final int kVoltageMode = 1;
026  private static final int kPositionMode = 2;
027  private static final int kVelocityMode = 3;
028  private static final int kFollowerMode = 4;
029
030  private ExpansionHub m_hub;
031  private final int m_channel;
032
033  private final DoubleSubscriber m_encoderSubscriber;
034  private final DoubleSubscriber m_encoderVelocitySubscriber;
035  private final DoubleSubscriber m_currentSubscriber;
036
037  private final DoublePublisher m_setpointPublisher;
038  private final BooleanPublisher m_floatOn0Publisher;
039  private final BooleanPublisher m_enabledPublisher;
040
041  private final IntegerPublisher m_modePublisher;
042
043  private final BooleanPublisher m_reversedPublisher;
044  private final BooleanPublisher m_resetEncoderPublisher;
045
046  private final DoublePublisher m_distancePerCountPublisher;
047
048  private final ExpansionHubPidConstants m_velocityPidConstants;
049  private final ExpansionHubPidConstants m_positionPidConstants;
050
051  /**
052   * Constructs a servo at the requested channel on a specific USB port.
053   *
054   * @param usbId The USB port ID the hub is connected to
055   * @param channel The motor channel
056   */
057  public ExpansionHubMotor(int usbId, int channel) {
058    m_hub = new ExpansionHub(usbId);
059    m_channel = channel;
060
061    if (!m_hub.checkMotorChannel(channel)) {
062      m_hub.close();
063      throw new IllegalArgumentException("Channel " + channel + " out of range");
064    }
065
066    if (!m_hub.checkAndReserveMotor(channel)) {
067      m_hub.close();
068      throw new AllocationException("ExpansionHub Motor already allocated");
069    }
070
071    m_hub.reportUsage("ExHubMotor[" + channel + "]", "ExHubMotor");
072
073    NetworkTableInstance systemServer = SystemServer.getSystemServer();
074
075    PubSubOption[] options =
076        new PubSubOption[] {
077          PubSubOption.SEND_ALL, PubSubOption.KEEP_DUPLICATES, PubSubOption.periodic(0.005)
078        };
079
080    m_encoderSubscriber =
081        systemServer
082            .getDoubleTopic("/rhsp/" + usbId + "/motor" + channel + "/encoder")
083            .subscribe(0, options);
084    m_encoderVelocitySubscriber =
085        systemServer
086            .getDoubleTopic("/rhsp/" + usbId + "/motor" + channel + "/encoderVelocity")
087            .subscribe(0, options);
088    m_currentSubscriber =
089        systemServer
090            .getDoubleTopic("/rhsp/" + usbId + "/motor" + channel + "/current")
091            .subscribe(0, options);
092
093    m_setpointPublisher =
094        systemServer
095            .getDoubleTopic("/rhsp/" + usbId + "/motor" + channel + "/setpoint")
096            .publish(options);
097
098    m_distancePerCountPublisher =
099        systemServer
100            .getDoubleTopic("/rhsp/" + usbId + "/motor" + channel + "/distancePerCount")
101            .publish(options);
102
103    m_floatOn0Publisher =
104        systemServer
105            .getBooleanTopic("/rhsp/" + usbId + "/motor" + channel + "/floatOn0")
106            .publish(options);
107    m_enabledPublisher =
108        systemServer
109            .getBooleanTopic("/rhsp/" + usbId + "/motor" + channel + "/enabled")
110            .publish(options);
111
112    m_modePublisher =
113        systemServer
114            .getIntegerTopic("/rhsp/" + usbId + "/motor" + channel + "/mode")
115            .publish(options);
116
117    m_reversedPublisher =
118        systemServer
119            .getBooleanTopic("/rhsp/" + usbId + "/motor" + channel + "/reversed")
120            .publish(options);
121
122    m_resetEncoderPublisher =
123        systemServer
124            .getBooleanTopic("/rhsp/" + usbId + "/motor" + channel + "/resetEncoder")
125            .publish(options);
126
127    m_velocityPidConstants = new ExpansionHubPidConstants(usbId, channel, true);
128    m_positionPidConstants = new ExpansionHubPidConstants(usbId, channel, false);
129  }
130
131  /** Closes a motor so another instance can be constructed. */
132  @Override
133  public void close() {
134    m_hub.unreserveMotor(m_channel);
135    m_hub.close();
136    m_hub = null;
137
138    m_encoderSubscriber.close();
139    m_encoderVelocitySubscriber.close();
140    m_currentSubscriber.close();
141    m_setpointPublisher.close();
142    m_floatOn0Publisher.close();
143    m_enabledPublisher.close();
144    m_modePublisher.close();
145    m_reversedPublisher.close();
146    m_resetEncoderPublisher.close();
147    m_distancePerCountPublisher.close();
148
149    m_velocityPidConstants.close();
150    m_positionPidConstants.close();
151  }
152
153  /**
154   * Sets the duty cycle.
155   *
156   * @param dutyCycle The duty cycle between -1 and 1 (sign indicates direction).
157   */
158  public void setDutyCycle(double dutyCycle) {
159    setEnabled(true);
160    m_modePublisher.set(kPercentageMode);
161    m_setpointPublisher.set(dutyCycle);
162  }
163
164  /**
165   * Sets the voltage to run the motor at. This value will be continously scaled to match the input
166   * voltage.
167   *
168   * @param voltage The voltage to drive the motor at
169   */
170  public void setVoltage(Voltage voltage) {
171    setEnabled(true);
172    m_modePublisher.set(kVoltageMode);
173    m_setpointPublisher.set(voltage.in(Volts));
174  }
175
176  /**
177   * Command the motor to drive to a specific position setpoint. This value will be scaled by
178   * setDistancePerCount and influenced by the PID constants.
179   *
180   * @param setpoint The position setpoint to drive the motor to
181   */
182  public void setPositionSetpoint(double setpoint) {
183    setEnabled(true);
184    m_modePublisher.set(kPositionMode);
185    m_setpointPublisher.set(setpoint);
186  }
187
188  /**
189   * Command the motor to drive to a specific velocity setpoint. This value will be scaled by
190   * setDistancePerCount and influenced by the PID constants.
191   *
192   * @param setpoint The velocity setpoint to drive the motor to
193   */
194  public void setVelocitySetpoint(double setpoint) {
195    setEnabled(true);
196    m_modePublisher.set(kVelocityMode);
197    m_setpointPublisher.set(setpoint);
198  }
199
200  /**
201   * Sets if the motor output is enabled or not. Defaults to false.
202   *
203   * @param enabled True to enable, false to disable
204   */
205  public void setEnabled(boolean enabled) {
206    m_enabledPublisher.set(enabled);
207  }
208
209  /**
210   * Sets if the motor should float or brake when 0 is commanded. Defaults to false.
211   *
212   * @param floatOn0 True to float when commanded 0, false to brake
213   */
214  public void setFloatOn0(boolean floatOn0) {
215    m_floatOn0Publisher.set(floatOn0);
216  }
217
218  /**
219   * Gets the current being pulled by the motor.
220   *
221   * @return Motor current
222   */
223  public Current getCurrent() {
224    return Amps.of(m_currentSubscriber.get(0));
225  }
226
227  /**
228   * Sets the distance per count of the encoder. Used to scale encoder readings.
229   *
230   * @param perCount The distance moved per count of the encoder
231   */
232  public void setDistancePerCount(double perCount) {
233    m_distancePerCountPublisher.set(perCount);
234  }
235
236  /**
237   * Gets if the underlying ExpansionHub is connected.
238   *
239   * @return True if hub is connected, otherwise false
240   */
241  public boolean isHubConnected() {
242    return m_hub.isHubConnected();
243  }
244
245  /**
246   * Gets the current velocity of the motor encoder. Scaled into distancePerCount units.
247   *
248   * @return Encoder velocity
249   */
250  public double getEncoderVelocity() {
251    return m_encoderVelocitySubscriber.get(0);
252  }
253
254  /**
255   * Gets the current position of the motor encoder. Scaled into distancePerCount units.
256   *
257   * @return Encoder position
258   */
259  public double getEncoderPosition() {
260    return m_encoderSubscriber.get(0);
261  }
262
263  /**
264   * Sets if the motor and encoder should be reversed.
265   *
266   * @param reversed True to reverse encoder, false otherwise
267   */
268  public void setReversed(boolean reversed) {
269    m_reversedPublisher.set(reversed);
270  }
271
272  /** Reset the encoder count to 0. */
273  public void resetEncoder() {
274    m_resetEncoderPublisher.set(true);
275  }
276
277  /**
278   * Gets the PID constants object for velocity PID.
279   *
280   * @return Velocity PID constants object
281   */
282  public ExpansionHubPidConstants getVelocityPidConstants() {
283    return m_velocityPidConstants;
284  }
285
286  /**
287   * Gets the PID constants object for position PID.
288   *
289   * @return Position PID constants object
290   */
291  public ExpansionHubPidConstants getPositionPidConstants() {
292    return m_positionPidConstants;
293  }
294
295  /**
296   * Sets this motor to follow another motor on the same hub.
297   *
298   * <p>This does not support following motors that are also followers. Additionally, the direction
299   * of both motors will be the same.
300   *
301   * @param leader The motor to follow
302   */
303  public void follow(ExpansionHubMotor leader) {
304    requireNonNullParam(leader, "leader", "follow");
305    if (leader.m_hub.getUsbId() != this.m_hub.getUsbId()) {
306      throw new IllegalArgumentException("Leader motor must be on the same hub as the follower");
307    }
308    m_modePublisher.set(kFollowerMode);
309    m_setpointPublisher.set(leader.m_channel);
310  }
311}