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 org.wpilib.hardware.hal.HAL;
008import org.wpilib.networktables.BooleanSubscriber;
009import org.wpilib.networktables.NetworkTableInstance;
010import org.wpilib.system.SystemServer;
011import org.wpilib.system.Timer;
012
013/** This class controls a REV ExpansionHub plugged in over USB to Systemcore. */
014public class ExpansionHub implements AutoCloseable {
015  private static class DataStore implements AutoCloseable {
016    private final int m_usbId;
017    private int m_refCount;
018    private int m_reservedMotorMask;
019    private int m_reservedServoMask;
020    private final Object m_reserveLock = new Object();
021
022    private final BooleanSubscriber m_hubConnectedSubscriber;
023
024    DataStore(int usbId) {
025      m_usbId = usbId;
026      m_storeMap[usbId] = this;
027
028      NetworkTableInstance systemServer = SystemServer.getSystemServer();
029
030      m_hubConnectedSubscriber =
031          systemServer.getBooleanTopic("/rhsp/" + usbId + "/connected").subscribe(false);
032
033      // Wait up to half a second for connected to come up, using a poll loop to
034      // ensure we don't block.
035      double startTime = Timer.getMonotonicTimestamp();
036      while (Timer.getMonotonicTimestamp() - startTime < 0.5) {
037        if (m_hubConnectedSubscriber.get(false)) {
038          break;
039        }
040        Timer.delay(0.01);
041      }
042    }
043
044    @Override
045    public void close() {
046      m_storeMap[m_usbId] = null;
047    }
048
049    public void addRef() {
050      m_refCount++;
051    }
052
053    public void removeRef() {
054      m_refCount--;
055      if (m_refCount == 0) {
056        this.close();
057      }
058    }
059  }
060
061  private static final DataStore[] m_storeMap = new DataStore[4];
062
063  private static void checkUsbId(int usbId) {
064    if (usbId < 0 || usbId > 3) {
065      throw new IllegalArgumentException("USB Port " + usbId + " out of range");
066    }
067  }
068
069  private static DataStore getForUsbId(int usbId) {
070    checkUsbId(usbId);
071    synchronized (m_storeMap) {
072      DataStore store = m_storeMap[usbId];
073      if (store == null) {
074        store = new DataStore(usbId);
075      }
076      store.addRef();
077      return store;
078    }
079  }
080
081  private static void freeHub(DataStore store) {
082    synchronized (m_storeMap) {
083      store.removeRef();
084    }
085  }
086
087  private final DataStore m_dataStore;
088
089  /**
090   * Constructs a new ExpansionHub for a given USB ID.
091   *
092   * <p>Multiple instances can be constructed, but will point to the same backing object with a ref
093   * count.
094   *
095   * @param usbId The USB Port ID the hub is plugged into.
096   */
097  public ExpansionHub(int usbId) {
098    m_dataStore = getForUsbId(usbId);
099  }
100
101  /**
102   * Closes an ExpansionHub object. Will not close any other instances until the last instance is
103   * closed.
104   */
105  @Override
106  public void close() {
107    freeHub(m_dataStore);
108  }
109
110  boolean checkServoChannel(int channel) {
111    return channel >= 0 && channel < 6;
112  }
113
114  boolean checkAndReserveServo(int channel) {
115    int mask = 1 << channel;
116    synchronized (m_dataStore.m_reserveLock) {
117      if ((m_dataStore.m_reservedServoMask & mask) != 0) {
118        return false;
119      }
120      m_dataStore.m_reservedServoMask |= mask;
121      return true;
122    }
123  }
124
125  void unreserveServo(int channel) {
126    int mask = 1 << channel;
127    synchronized (m_dataStore.m_reserveLock) {
128      m_dataStore.m_reservedServoMask &= ~mask;
129    }
130  }
131
132  boolean checkMotorChannel(int channel) {
133    return channel >= 0 && channel < 4;
134  }
135
136  boolean checkAndReserveMotor(int channel) {
137    int mask = 1 << channel;
138    synchronized (m_dataStore.m_reserveLock) {
139      if ((m_dataStore.m_reservedMotorMask & mask) != 0) {
140        return false;
141      }
142      m_dataStore.m_reservedMotorMask |= mask;
143      return true;
144    }
145  }
146
147  void unreserveMotor(int channel) {
148    int mask = 1 << channel;
149    synchronized (m_dataStore.m_reserveLock) {
150      m_dataStore.m_reservedMotorMask &= ~mask;
151    }
152  }
153
154  void reportUsage(String device, String data) {
155    HAL.reportUsage("ExpansionHub[" + m_dataStore.m_usbId + "]/" + device, data);
156  }
157
158  /**
159   * Constructs a servo at the requested channel on this hub.
160   *
161   * <p>Only a single instance of each servo per hub can be constructed at a time.
162   *
163   * @param channel The servo channel
164   * @return Servo object
165   */
166  public ExpansionHubServo makeServo(int channel) {
167    return new ExpansionHubServo(m_dataStore.m_usbId, channel);
168  }
169
170  /**
171   * Constructs a continuous rotation servo at the requested channel on this hub.
172   *
173   * <p>Only a single instance of each servo per hub can be constructed at a time.
174   *
175   * @param channel The servo channel
176   * @return Continuous rotation servo object
177   */
178  public ExpansionHubCRServo makeCRServo(int channel) {
179    return new ExpansionHubCRServo(m_dataStore.m_usbId, channel);
180  }
181
182  /**
183   * Constructs a motor at the requested channel on this hub.
184   *
185   * <p>Only a single instance of each motor per hub can be constructed at a time.
186   *
187   * @param channel The motor channel
188   * @return Motor object
189   */
190  public ExpansionHubMotor makeMotor(int channel) {
191    return new ExpansionHubMotor(m_dataStore.m_usbId, channel);
192  }
193
194  /**
195   * Gets if the hub is currently connected over USB.
196   *
197   * @return True if hub connection, otherwise false
198   */
199  public boolean isHubConnected() {
200    return m_dataStore.m_hubConnectedSubscriber.get(false);
201  }
202
203  /**
204   * Gets the USB ID of this hub.
205   *
206   * @return The USB ID
207   */
208  public int getUsbId() {
209    return m_dataStore.m_usbId;
210  }
211}