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}