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}