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.Degrees; 008import static org.wpilib.units.Units.Microseconds; 009 010import org.wpilib.hardware.hal.util.AllocationException; 011import org.wpilib.networktables.BooleanPublisher; 012import org.wpilib.networktables.IntegerPublisher; 013import org.wpilib.networktables.NetworkTableInstance; 014import org.wpilib.networktables.PubSubOption; 015import org.wpilib.system.SystemServer; 016import org.wpilib.units.measure.Angle; 017import org.wpilib.units.measure.Time; 018 019/** This class controls a specific servo in positional/servo mode hooked up to an ExpansionHub. */ 020public class ExpansionHubServo implements AutoCloseable { 021 private ExpansionHub m_hub; 022 private final int m_channel; 023 024 private boolean m_reversed; 025 026 private final IntegerPublisher m_pulseWidthPublisher; 027 private final IntegerPublisher m_framePeriodPublisher; 028 private final BooleanPublisher m_enabledPublisher; 029 030 private double m_maxServoAngle = 180.0; 031 private double m_minServoAngle; 032 033 private int m_minPwm = 600; 034 private int m_maxPwm = 2400; 035 036 /** 037 * Constructs a servo at the requested channel on a specific USB port. 038 * 039 * @param usbId The USB port ID the hub is connected to 040 * @param channel The servo channel 041 */ 042 public ExpansionHubServo(int usbId, int channel) { 043 m_hub = new ExpansionHub(usbId); 044 m_channel = channel; 045 046 if (!m_hub.checkServoChannel(channel)) { 047 m_hub.close(); 048 throw new IllegalArgumentException("Channel " + channel + " out of range"); 049 } 050 051 if (!m_hub.checkAndReserveServo(channel)) { 052 m_hub.close(); 053 throw new AllocationException("ExpansionHub Servo already allocated"); 054 } 055 056 m_hub.reportUsage("ExHubServo[" + channel + "]", "ExHubServo"); 057 058 NetworkTableInstance systemServer = SystemServer.getSystemServer(); 059 060 PubSubOption[] options = 061 new PubSubOption[] { 062 PubSubOption.SEND_ALL, PubSubOption.KEEP_DUPLICATES, PubSubOption.periodic(0.005) 063 }; 064 065 m_pulseWidthPublisher = 066 systemServer 067 .getIntegerTopic("/rhsp/" + usbId + "/servo" + channel + "/pulseWidth") 068 .publish(options); 069 070 m_pulseWidthPublisher.set(1500); 071 072 m_framePeriodPublisher = 073 systemServer 074 .getIntegerTopic("/rhsp/" + usbId + "/servo" + channel + "/framePeriod") 075 .publish(options); 076 077 m_framePeriodPublisher.set(20000); 078 079 m_enabledPublisher = 080 systemServer 081 .getBooleanTopic("/rhsp/" + usbId + "/servo" + channel + "/enabled") 082 .publish(options); 083 } 084 085 /** 086 * Set the servo position. 087 * 088 * <p>Servo values range from 0.0 to 1.0 corresponding to the range of full left to full right. 089 * 090 * @param value Position from 0.0 to 1.0. 091 */ 092 public void setPosition(double value) { 093 value = Math.clamp(value, 0.0, 1.0); 094 095 if (m_reversed) { 096 value = 1.0 - value; 097 } 098 099 int rawValue = (int) ((value * getFullRangeScaleFactor()) + m_minPwm); 100 101 setEnabled(true); 102 m_pulseWidthPublisher.set(rawValue); 103 } 104 105 /** 106 * Sets the servo angle. 107 * 108 * <p>Servo angles range defaults to 0 to 180 degrees, but can be changed with setAngleRange(). 109 * 110 * @param angle Position in angle units. Will be clamped to be within the current angle range. 111 */ 112 public void setAngle(Angle angle) { 113 double dAngle = angle.in(Degrees); 114 if (dAngle < m_minServoAngle) { 115 dAngle = m_minServoAngle; 116 } else if (dAngle > m_maxServoAngle) { 117 dAngle = m_maxServoAngle; 118 } 119 120 setPosition((dAngle - m_minServoAngle) / getServoAngleRange()); 121 } 122 123 private double getFullRangeScaleFactor() { 124 return m_maxPwm - m_minPwm; 125 } 126 127 private double getServoAngleRange() { 128 return m_maxServoAngle - m_minServoAngle; 129 } 130 131 /** 132 * Sets the raw pulse width output on the servo. 133 * 134 * @param pulseWidth Pulse width 135 */ 136 public void setPulseWidth(Time pulseWidth) { 137 setEnabled(true); 138 m_pulseWidthPublisher.set((long) pulseWidth.in(Microseconds)); 139 } 140 141 /** 142 * Sets if the servo output is enabled or not. Defaults to false. 143 * 144 * @param enabled True to enable, false to disable 145 */ 146 public void setEnabled(boolean enabled) { 147 m_enabledPublisher.set(enabled); 148 } 149 150 /** 151 * Sets the frame period for the servo. Defaults to 20ms. 152 * 153 * @param framePeriod The frame period 154 */ 155 public void setFramePeriod(Time framePeriod) { 156 m_framePeriodPublisher.set((long) framePeriod.in(Microseconds)); 157 } 158 159 /** 160 * Gets if the underlying ExpansionHub is connected. 161 * 162 * @return True if hub is connected, otherwise false 163 */ 164 public boolean isHubConnected() { 165 return m_hub.isHubConnected(); 166 } 167 168 /** 169 * Sets whether the servo is reversed. 170 * 171 * <p>This will reverse both setPosition() and setAngle(). 172 * 173 * @param reversed True to reverse, false for normal 174 */ 175 public void setReversed(boolean reversed) { 176 m_reversed = reversed; 177 } 178 179 /** 180 * Sets the PWM range for the servo. By default, this is 600 to 2400 microseconds. 181 * 182 * <p>Maximum must be greater than minimum. 183 * 184 * @param minPwm Minimum PWM 185 * @param maxPwm Maximum PWM 186 */ 187 public void setPWMRange(int minPwm, int maxPwm) { 188 if (maxPwm <= minPwm) { 189 throw new IllegalArgumentException("Maximum PWM must be greater than minimum PWM"); 190 } 191 m_minPwm = minPwm; 192 m_maxPwm = maxPwm; 193 } 194 195 /** 196 * Sets the angle range for the setAngle call. By default, this is 0 to 180 degrees. 197 * 198 * <p>Maximum angle must be greater than minimum angle. 199 * 200 * @param minAngle Minimum angle 201 * @param maxAngle Maximum angle 202 */ 203 public void setAngleRange(double minAngle, double maxAngle) { 204 if (maxAngle <= minAngle) { 205 throw new IllegalArgumentException("Maximum angle must be greater than minimum angle"); 206 } 207 m_minServoAngle = minAngle; 208 m_maxServoAngle = maxAngle; 209 } 210 211 /** Closes a servo so another instance can be constructed. */ 212 @Override 213 public void close() { 214 m_hub.unreserveServo(m_channel); 215 m_hub.close(); 216 m_hub = null; 217 218 m_pulseWidthPublisher.close(); 219 m_framePeriodPublisher.close(); 220 m_enabledPublisher.close(); 221 } 222}