[wpilib] Integrate support for ExpansionHub over USB (#8292)

This commit is contained in:
Thad House
2025-11-01 16:24:58 +00:00
committed by GitHub
parent 4da2511638
commit def7849909
12 changed files with 1875 additions and 0 deletions

View File

@@ -0,0 +1,187 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package edu.wpi.first.wpilibj;
import edu.wpi.first.hal.HAL;
import edu.wpi.first.networktables.BooleanSubscriber;
import edu.wpi.first.networktables.NetworkTableInstance;
/** This class controls a REV ExpansionHub plugged in over USB to Systemcore. */
public class ExpansionHub implements AutoCloseable {
private static class DataStore implements AutoCloseable {
public final int m_usbId;
private int m_refCount;
private int m_reservedMotorMask;
private int m_reservedServoMask;
private final Object m_reserveLock = new Object();
private final BooleanSubscriber m_hubConnectedSubscriber;
DataStore(int usbId) {
m_usbId = usbId;
m_storeMap[usbId] = this;
NetworkTableInstance systemServer = SystemServer.getSystemServer();
m_hubConnectedSubscriber =
systemServer.getBooleanTopic("/rhsp/" + usbId + "/connected").subscribe(false);
}
@Override
public void close() {
m_storeMap[m_usbId] = null;
}
public void addRef() {
m_refCount++;
}
public void removeRef() {
m_refCount--;
if (m_refCount == 0) {
this.close();
}
}
}
private static final DataStore[] m_storeMap = new DataStore[4];
private static void checkUsbId(int usbId) {
if (usbId < 0 || usbId > 3) {
throw new IllegalArgumentException("USB Port " + usbId + " out of range");
}
}
private static DataStore getForUsbId(int usbId) {
checkUsbId(usbId);
synchronized (m_storeMap) {
DataStore store = m_storeMap[usbId];
if (store == null) {
store = new DataStore(usbId);
}
store.addRef();
return store;
}
}
private static void freeHub(DataStore store) {
synchronized (m_storeMap) {
store.removeRef();
}
}
private final DataStore m_dataStore;
/**
* Constructs a new ExpansionHub for a given USB ID
*
* <p>Multiple instances can be constructed, but will point to the same backing object with a ref
* count.
*
* @param usbId The USB Port ID the hub is plugged into.
*/
public ExpansionHub(int usbId) {
m_dataStore = getForUsbId(usbId);
}
/**
* Closes an ExpansionHub object. Will not close any other instances until the last instance is
* closed.
*/
@Override
public void close() {
freeHub(m_dataStore);
}
boolean checkServoChannel(int channel) {
return channel >= 0 && channel < 6;
}
boolean checkAndReserveServo(int channel) {
int mask = 1 << channel;
synchronized (m_dataStore.m_reserveLock) {
if ((m_dataStore.m_reservedServoMask & mask) != 0) {
return false;
}
m_dataStore.m_reservedServoMask |= mask;
return true;
}
}
void unreserveServo(int channel) {
int mask = 1 << channel;
synchronized (m_dataStore.m_reserveLock) {
m_dataStore.m_reservedServoMask &= ~mask;
}
}
boolean checkMotorChannel(int channel) {
return channel >= 0 && channel < 4;
}
boolean checkAndReserveMotor(int channel) {
int mask = 1 << channel;
synchronized (m_dataStore.m_reserveLock) {
if ((m_dataStore.m_reservedMotorMask & mask) != 0) {
return false;
}
m_dataStore.m_reservedMotorMask |= mask;
return true;
}
}
void unreserveMotor(int channel) {
int mask = 1 << channel;
synchronized (m_dataStore.m_reserveLock) {
m_dataStore.m_reservedMotorMask &= ~mask;
}
}
void reportUsage(String device, String data) {
HAL.reportUsage("ExpansionHub[" + m_dataStore.m_usbId + "]/" + device, data);
}
/**
* Constructs a servo at the requested channel on this hub.
*
* <p>Only a single instance of each servo per hub can be constructed at a time.
*
* @param channel The servo channel
* @return Servo object
*/
public ExpansionHubServo makeServo(int channel) {
return new ExpansionHubServo(m_dataStore.m_usbId, channel);
}
/**
* Constructs a motor at the requested channel on this hub.
*
* <p>Only a single instance of each motor per hub can be constructed at a time.
*
* @param channel The motor channel
* @return Motor object
*/
public ExpansionHubMotor makeMotor(int channel) {
return new ExpansionHubMotor(m_dataStore.m_usbId, channel);
}
/**
* Gets if the hub is currently connected over USB.
*
* @return True if hub connection, otherwise false
*/
public boolean isHubConnected() {
return m_dataStore.m_hubConnectedSubscriber.get(false);
}
/**
* Gets the USB ID of this hub.
*
* @return The USB ID
*/
public int getUsbId() {
return m_dataStore.m_usbId;
}
}

View File

@@ -0,0 +1,308 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package edu.wpi.first.wpilibj;
import static edu.wpi.first.units.Units.Amps;
import static edu.wpi.first.units.Units.Volts;
import static edu.wpi.first.util.ErrorMessages.requireNonNullParam;
import edu.wpi.first.hal.util.AllocationException;
import edu.wpi.first.networktables.BooleanPublisher;
import edu.wpi.first.networktables.DoublePublisher;
import edu.wpi.first.networktables.DoubleSubscriber;
import edu.wpi.first.networktables.IntegerPublisher;
import edu.wpi.first.networktables.NetworkTableInstance;
import edu.wpi.first.networktables.PubSubOption;
import edu.wpi.first.units.measure.Current;
import edu.wpi.first.units.measure.Voltage;
/** This class controls a specific motor and encoder hooked up to an ExpansionHub. */
public class ExpansionHubMotor implements AutoCloseable {
private static final int kPercentageMode = 0;
private static final int kVoltageMode = 1;
private static final int kPositionMode = 2;
private static final int kVelocityMode = 3;
private static final int kFollowerMode = 4;
private ExpansionHub m_hub;
private final int m_channel;
private final DoubleSubscriber m_encoderSubscriber;
private final DoubleSubscriber m_encoderVelocitySubscriber;
private final DoubleSubscriber m_currentSubscriber;
private final DoublePublisher m_setpointPublisher;
private final BooleanPublisher m_floatOn0Publisher;
private final BooleanPublisher m_enabledPublisher;
private final IntegerPublisher m_modePublisher;
private final BooleanPublisher m_reversedPublisher;
private final BooleanPublisher m_resetEncoderPublisher;
private final DoublePublisher m_distancePerCountPublisher;
private final ExpansionHubPidConstants m_velocityPidConstants;
private final ExpansionHubPidConstants m_positionPidConstants;
/**
* Constructs a servo at the requested channel on a specific USB port.
*
* @param usbId The USB port ID the hub is connected to
* @param channel The motor channel
*/
public ExpansionHubMotor(int usbId, int channel) {
m_hub = new ExpansionHub(usbId);
m_channel = channel;
if (!m_hub.checkMotorChannel(channel)) {
m_hub.close();
throw new IllegalArgumentException("Channel " + channel + " out of range");
}
if (!m_hub.checkAndReserveMotor(channel)) {
m_hub.close();
throw new AllocationException("ExpansionHub Motor already allocated");
}
m_hub.reportUsage("ExHubMotor[" + channel + "]", "ExHubMotor");
NetworkTableInstance systemServer = SystemServer.getSystemServer();
PubSubOption[] options =
new PubSubOption[] {
PubSubOption.sendAll(true),
PubSubOption.keepDuplicates(true),
PubSubOption.periodic(0.005)
};
m_encoderSubscriber =
systemServer
.getDoubleTopic("/rhsp/" + usbId + "/motor" + channel + "/encoder")
.subscribe(0, options);
m_encoderVelocitySubscriber =
systemServer
.getDoubleTopic("/rhsp/" + usbId + "/motor" + channel + "/encoderVelocity")
.subscribe(0, options);
m_currentSubscriber =
systemServer
.getDoubleTopic("/rhsp/" + usbId + "/motor" + channel + "/current")
.subscribe(0, options);
m_setpointPublisher =
systemServer
.getDoubleTopic("/rhsp/" + usbId + "/motor" + channel + "/setpoint")
.publish(options);
m_distancePerCountPublisher =
systemServer
.getDoubleTopic("/rhsp/" + usbId + "/motor" + channel + "/distancePerCount")
.publish(options);
m_floatOn0Publisher =
systemServer
.getBooleanTopic("/rhsp/" + usbId + "/motor" + channel + "/floatOn0")
.publish(options);
m_enabledPublisher =
systemServer
.getBooleanTopic("/rhsp/" + usbId + "/motor" + channel + "/enabled")
.publish(options);
m_modePublisher =
systemServer
.getIntegerTopic("/rhsp/" + usbId + "/motor" + channel + "/mode")
.publish(options);
m_reversedPublisher =
systemServer
.getBooleanTopic("/rhsp/" + usbId + "/motor" + channel + "/reversed")
.publish(options);
m_resetEncoderPublisher =
systemServer
.getBooleanTopic("/rhsp/" + usbId + "/motor" + channel + "/resetEncoder")
.publish(options);
m_velocityPidConstants = new ExpansionHubPidConstants(usbId, channel, true);
m_positionPidConstants = new ExpansionHubPidConstants(usbId, channel, false);
}
/** Closes a motor so another instance can be constructed. */
@Override
public void close() {
m_hub.unreserveMotor(m_channel);
m_hub.close();
m_hub = null;
m_encoderSubscriber.close();
m_encoderVelocitySubscriber.close();
m_currentSubscriber.close();
m_setpointPublisher.close();
m_floatOn0Publisher.close();
m_enabledPublisher.close();
m_modePublisher.close();
m_reversedPublisher.close();
m_resetEncoderPublisher.close();
m_distancePerCountPublisher.close();
m_velocityPidConstants.close();
m_positionPidConstants.close();
}
/**
* Sets the percentage power to run the motor at, between -1 and 1.
*
* @param power The power to drive the motor at
*/
public void setPercentagePower(double power) {
m_modePublisher.set(kPercentageMode);
m_setpointPublisher.set(power);
}
/**
* Sets the voltage to run the motor at. This value will be continously scaled to match the input
* voltage.
*
* @param voltage The voltage to drive the motor at
*/
public void setVoltage(Voltage voltage) {
m_modePublisher.set(kVoltageMode);
m_setpointPublisher.set(voltage.in(Volts));
}
/**
* Command the motor to drive to a specific position setpoint. This value will be scaled by
* setDistancePerCount and influenced by the PID constants.
*
* @param setpoint The position setpoint to drive the motor to
*/
public void setPositionSetpoint(double setpoint) {
m_modePublisher.set(kPositionMode);
m_setpointPublisher.set(setpoint);
}
/**
* Command the motor to drive to a specific velocity setpoint. This value will be scaled by
* setDistancePerCount and influenced by the PID constants.
*
* @param setpoint The velocity setpoint to drive the motor to
*/
public void setVelocitySetpoint(double setpoint) {
m_modePublisher.set(kVelocityMode);
m_setpointPublisher.set(setpoint);
}
/**
* Sets if the motor output is enabled or not. Defaults to false.
*
* @param enabled True to enable, false to disable
*/
public void setEnabled(boolean enabled) {
m_enabledPublisher.set(enabled);
}
/**
* Sets if the motor should float or brake when 0 is commanded. Defaults to false.
*
* @param floatOn0 True to float when commanded 0, false to brake
*/
public void setFloatOn0(boolean floatOn0) {
m_floatOn0Publisher.set(floatOn0);
}
/**
* Gets the current being pulled by the motor.
*
* @return Motor current
*/
public Current getCurrent() {
return Amps.of(m_currentSubscriber.get(0));
}
/**
* Sets the distance per count of the encoder. Used to scale encoder readings.
*
* @param perCount The distance moved per count of the encoder
*/
public void setDistancePerCount(double perCount) {
m_distancePerCountPublisher.set(perCount);
}
/**
* Gets if the underlying ExpansionHub is connected.
*
* @return True if hub is connected, otherwise false
*/
public boolean isHubConnected() {
return m_hub.isHubConnected();
}
/**
* Gets the current velocity of the motor encoder. Scaled into distancePerCount units.
*
* @return Encoder velocity
*/
public double getEncoderVelocity() {
return m_encoderVelocitySubscriber.get(0);
}
/**
* Gets the current position of the motor encoder. Scaled into distancePerCount units.
*
* @return Encoder position
*/
public double getEncoderPosition() {
return m_encoderSubscriber.get(0);
}
/**
* Sets if the motor and encoder should be reversed.
*
* @param reversed True to reverse encoder, false otherwise
*/
public void setReversed(boolean reversed) {
m_reversedPublisher.set(reversed);
}
/** Reset the encoder count to 0. */
public void resetEncoder() {
m_resetEncoderPublisher.set(true);
}
/**
* Gets the PID constants object for velocity PID.
*
* @return Velocity PID constants object
*/
public ExpansionHubPidConstants getVelocityPidConstants() {
return m_velocityPidConstants;
}
/**
* Gets the PID constants object for position PID.
*
* @return Position PID constants object
*/
public ExpansionHubPidConstants getPositionPidConstants() {
return m_positionPidConstants;
}
/**
* Sets this motor to follow another motor on the same hub.
*
* <p>This does not support following motors that are also followers. Additionally, the direction
* of both motors will be the same.
*
* @param leader The motor to follow
*/
public void follow(ExpansionHubMotor leader) {
requireNonNullParam(leader, "leader", "follow");
if (leader.m_hub.getUsbId() != this.m_hub.getUsbId()) {
throw new IllegalArgumentException("Leader motor must be on the same hub as the follower");
}
m_modePublisher.set(kFollowerMode);
m_setpointPublisher.set(leader.m_channel);
}
}

View File

@@ -0,0 +1,166 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package edu.wpi.first.wpilibj;
import edu.wpi.first.networktables.BooleanPublisher;
import edu.wpi.first.networktables.DoublePublisher;
import edu.wpi.first.networktables.NetworkTableInstance;
import edu.wpi.first.networktables.PubSubOption;
/** This class contains PID constants for an ExpansionHub motor. */
public class ExpansionHubPidConstants {
private final DoublePublisher m_pPublisher;
private final DoublePublisher m_iPublisher;
private final DoublePublisher m_dPublisher;
private final DoublePublisher m_sPublisher;
private final DoublePublisher m_vPublisher;
private final DoublePublisher m_aPublisher;
private final BooleanPublisher m_continuousPublisher;
private final DoublePublisher m_continuousMinimumPublisher;
private final DoublePublisher m_continuousMaximumPublisher;
ExpansionHubPidConstants(int hubNumber, int motorNumber, boolean isVelocityPid) {
NetworkTableInstance systemServer = SystemServer.getSystemServer();
PubSubOption[] options =
new PubSubOption[] {
PubSubOption.sendAll(true),
PubSubOption.keepDuplicates(true),
PubSubOption.periodic(0.005)
};
String pidType = isVelocityPid ? "velocity" : "position";
m_pPublisher =
systemServer
.getDoubleTopic(
"/rhsp/" + hubNumber + "/motor" + motorNumber + "/pid/" + pidType + "/kp")
.publish(options);
m_iPublisher =
systemServer
.getDoubleTopic(
"/rhsp/" + hubNumber + "/motor" + motorNumber + "/pid/" + pidType + "/ki")
.publish(options);
m_dPublisher =
systemServer
.getDoubleTopic(
"/rhsp/" + hubNumber + "/motor" + motorNumber + "/pid/" + pidType + "/kd")
.publish(options);
m_aPublisher =
systemServer
.getDoubleTopic(
"/rhsp/" + hubNumber + "/motor" + motorNumber + "/pid/" + pidType + "/ka")
.publish(options);
m_vPublisher =
systemServer
.getDoubleTopic(
"/rhsp/" + hubNumber + "/motor" + motorNumber + "/pid/" + pidType + "/kv")
.publish(options);
m_sPublisher =
systemServer
.getDoubleTopic(
"/rhsp/" + hubNumber + "/motor" + motorNumber + "/pid/" + pidType + "/ks")
.publish(options);
m_continuousPublisher =
systemServer
.getBooleanTopic(
"/rhsp/" + hubNumber + "/motor" + motorNumber + "/pid/" + pidType + "/continuous")
.publish(options);
m_continuousMinimumPublisher =
systemServer
.getDoubleTopic(
"/rhsp/"
+ hubNumber
+ "/motor"
+ motorNumber
+ "/pid/"
+ pidType
+ "/continuousMinimum")
.publish(options);
m_continuousMaximumPublisher =
systemServer
.getDoubleTopic(
"/rhsp/"
+ hubNumber
+ "/motor"
+ motorNumber
+ "/pid/"
+ pidType
+ "/continousMaximum")
.publish(options);
}
/**
* Sets the PID Controller gain parameters.
*
* <p>Set the proportional, integral, and differential coefficients.
*
* @param p The proportional coefficient.
* @param i The integral coefficient.
* @param d The derivative coefficient.
*/
public void setPID(double p, double i, double d) {
m_pPublisher.set(p);
m_iPublisher.set(i);
m_dPublisher.set(d);
}
/**
* Sets the feed forward gains to the specified values.
*
* <p>The units should be radians for angular systems and meters for linear systems.
*
* <p>The PID control period is 10ms
*
* @param s The static gain in volts.
* @param v The velocity gain in V/(units/s).
* @param a The acceleration gain in V/(units/s²).
*/
public void setFF(double s, double v, double a) {
m_sPublisher.set(s);
m_vPublisher.set(v);
m_aPublisher.set(a);
}
/**
* Enables continuous input.
*
* <p>Rather then using the max and min input range as constraints, it considers them to be the
* same point and automatically calculates the shortest route to the setpoint.
*
* @param minimumInput The minimum value expected from the input.
* @param maximumInput The maximum value expected from the input.
*/
public void enableContinuousInput(double minimumInput, double maximumInput) {
m_continuousMaximumPublisher.set(maximumInput);
m_continuousMinimumPublisher.set(minimumInput);
m_continuousPublisher.set(true);
}
/** Disable continous input mode. */
public void disableContinuousInput() {
m_continuousPublisher.set(false);
}
void close() {
m_iPublisher.close();
m_dPublisher.close();
m_sPublisher.close();
m_vPublisher.close();
m_aPublisher.close();
m_continuousPublisher.close();
m_continuousMinimumPublisher.close();
m_continuousMaximumPublisher.close();
}
}

View File

@@ -0,0 +1,240 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package edu.wpi.first.wpilibj;
import static edu.wpi.first.units.Units.Degrees;
import static edu.wpi.first.units.Units.Microseconds;
import edu.wpi.first.hal.util.AllocationException;
import edu.wpi.first.networktables.BooleanPublisher;
import edu.wpi.first.networktables.IntegerPublisher;
import edu.wpi.first.networktables.NetworkTableInstance;
import edu.wpi.first.networktables.PubSubOption;
import edu.wpi.first.units.measure.Angle;
import edu.wpi.first.units.measure.Time;
/** This class controls a specific servo hooked up to an ExpansionHub. */
public class ExpansionHubServo implements AutoCloseable {
private ExpansionHub m_hub;
private final int m_channel;
private boolean m_reversed;
private boolean m_continousMode;
private final IntegerPublisher m_pulseWidthPublisher;
private final IntegerPublisher m_framePeriodPublisher;
private final BooleanPublisher m_enabledPublisher;
private double m_maxServoAngle = 180.0;
private double m_minServoAngle;
private int m_minPwm = 600;
private int m_maxPwm = 2400;
/**
* Constructs a servo at the requested channel on a specific USB port.
*
* @param usbId The USB port ID the hub is connected to
* @param channel The servo channel
*/
public ExpansionHubServo(int usbId, int channel) {
m_hub = new ExpansionHub(usbId);
m_channel = channel;
if (!m_hub.checkServoChannel(channel)) {
m_hub.close();
throw new IllegalArgumentException("Channel " + channel + " out of range");
}
if (!m_hub.checkAndReserveServo(channel)) {
m_hub.close();
throw new AllocationException("ExpansionHub Servo already allocated");
}
m_hub.reportUsage("ExHubServo[" + channel + "]", "ExHubServo");
NetworkTableInstance systemServer = SystemServer.getSystemServer();
PubSubOption[] options =
new PubSubOption[] {
PubSubOption.sendAll(true),
PubSubOption.keepDuplicates(true),
PubSubOption.periodic(0.005)
};
m_pulseWidthPublisher =
systemServer
.getIntegerTopic("/rhsp/" + usbId + "/servo" + channel + "/pulseWidth")
.publish(options);
m_pulseWidthPublisher.set(1500);
m_framePeriodPublisher =
systemServer
.getIntegerTopic("/rhsp/" + usbId + "/servo" + channel + "/framePeriod")
.publish(options);
m_framePeriodPublisher.set(20000);
m_enabledPublisher =
systemServer
.getBooleanTopic("/rhsp/" + usbId + "/servo" + channel + "/enabled")
.publish(options);
}
/**
* Set the servo position.
*
* <p>Servo values range from 0.0 to 1.0 corresponding to the range of full left to full right. If
* continuous rotation mode is enabled, the range is -1.0 to 1.0.
*
* @param value Position from 0.0 to 1.0 (-1 to 1 in CR mode).
*/
public void set(double value) {
if (m_continousMode) {
value = Math.clamp(value, -1.0, 1.0);
value = (value + 1.0) / 2.0;
}
value = Math.clamp(value, 0.0, 1.0);
if (m_reversed) {
value = 1.0 - value;
}
int rawValue = (int) ((value * getFullRangeScaleFactor()) + m_minPwm);
m_pulseWidthPublisher.set(rawValue);
}
/**
* Sets the servo angle
*
* <p>Servo angles range defaults to 0 to 180 degrees, but can be changed with setAngleRange().
*
* @param angle Position in angle units. Will be scaled between the current angle range.
*/
public void setAngle(Angle angle) {
double dAngle = angle.in(Degrees);
if (dAngle < m_minServoAngle) {
dAngle = m_minServoAngle;
} else if (dAngle > m_maxServoAngle) {
dAngle = m_maxServoAngle;
}
set((dAngle - m_minServoAngle) / getServoAngleRange());
}
private double getFullRangeScaleFactor() {
return m_maxPwm - m_minPwm;
}
private double getServoAngleRange() {
return m_maxServoAngle - m_minServoAngle;
}
/**
* Sets the raw pulse width output on the servo.
*
* @param pulseWidth Pulse width
*/
public void setPulseWidth(Time pulseWidth) {
m_pulseWidthPublisher.set((long) pulseWidth.in(Microseconds));
}
/**
* Sets if the servo output is enabled or not. Defaults to false.
*
* @param enabled True to enable, false to disable
*/
public void setEnabled(boolean enabled) {
m_enabledPublisher.set(enabled);
}
/**
* Sets the frame period for the servo. Defaults to 20ms.
*
* @param framePeriod The frame period
*/
public void setFramePeriod(Time framePeriod) {
m_framePeriodPublisher.set((long) framePeriod.in(Microseconds));
}
/**
* Gets if the underlying ExpansionHub is connected.
*
* @return True if hub is connected, otherwise false
*/
public boolean isHubConnected() {
return m_hub.isHubConnected();
}
/**
* Sets whether the servo is reversed.
*
* <p>This will reverse both set() and setAngle().
*
* @param reversed True to reverse, false for normal
*/
public void setReversed(boolean reversed) {
m_reversed = reversed;
}
/**
* Sets the PWM range for the servo. By default, this is 600 to 2400 microseconds.
*
* <p>Maximum must be greater than minimum.
*
* @param minPwm Minimum PWM
* @param maxPwm Maximum PWM
*/
public void setPWMRange(int minPwm, int maxPwm) {
if (maxPwm <= minPwm) {
throw new IllegalArgumentException("Maximum PWM must be greater than minimum PWM");
}
m_minPwm = minPwm;
m_maxPwm = maxPwm;
}
/**
* Sets the angle range for the setAngle call. By default, this is 0 to 180 degrees.
*
* <p>Maximum angle must be greater than minimum angle.
*
* @param minAngle Minimum angle
* @param maxAngle Maximum angle
*/
public void setAngleRange(double minAngle, double maxAngle) {
if (maxAngle <= minAngle) {
throw new IllegalArgumentException("Maximum angle must be greater than minimum angle");
}
m_minServoAngle = minAngle;
m_maxServoAngle = maxAngle;
}
/**
* Enables or disables continuous rotation mode.
*
* <p>In continuous rotation mode, the servo will interpret Set() commands to between -1.0 and
* 1.0, instead of 0.0 to 1.0.
*
* @param enable True to enable continuous rotation mode, false to disable
*/
public void setContinousRotationMode(boolean enable) {
m_continousMode = enable;
}
/** Closes a servo so another instance can be constructed. */
@Override
public void close() {
m_hub.unreserveServo(m_channel);
m_hub.close();
m_hub = null;
m_pulseWidthPublisher.close();
m_framePeriodPublisher.close();
m_enabledPublisher.close();
}
}