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