Brian Silverman | f7f267a | 2017-02-04 16:16:08 -0800 | [diff] [blame^] | 1 | /*----------------------------------------------------------------------------*/ |
| 2 | /* Copyright (c) FIRST 2016-2017. All Rights Reserved. */ |
| 3 | /* Open Source Software - may be modified and shared by FRC teams. The code */ |
| 4 | /* must be accompanied by the FIRST BSD license file in the root directory of */ |
| 5 | /* the project. */ |
| 6 | /*----------------------------------------------------------------------------*/ |
| 7 | |
| 8 | #include "HAL/cpp/SerialHelper.h" |
| 9 | |
| 10 | #include <algorithm> |
| 11 | #include <cstdio> |
| 12 | #include <cstring> |
| 13 | |
| 14 | #include "../visa/visa.h" |
| 15 | #include "HAL/Errors.h" |
| 16 | #include "llvm/StringRef.h" |
| 17 | |
| 18 | constexpr const char* OnboardResourceVISA = "ASRL1::INSTR"; |
| 19 | constexpr const char* MxpResourceVISA = "ASRL2::INSTR"; |
| 20 | |
| 21 | constexpr const char* OnboardResourceOS = "/dev/ttyS0"; |
| 22 | constexpr const char* MxpResourceOS = "/dev/ttyS1"; |
| 23 | |
| 24 | namespace hal { |
| 25 | std::string SerialHelper::m_usbNames[2]{"", ""}; |
| 26 | |
| 27 | priority_mutex SerialHelper::m_nameMutex; |
| 28 | |
| 29 | SerialHelper::SerialHelper() { |
| 30 | viOpenDefaultRM(reinterpret_cast<ViSession*>(&m_resourceHandle)); |
| 31 | } |
| 32 | |
| 33 | std::string SerialHelper::GetVISASerialPortName(HAL_SerialPort port, |
| 34 | int32_t* status) { |
| 35 | if (port == HAL_SerialPort::HAL_SerialPort_Onboard) { |
| 36 | return OnboardResourceVISA; |
| 37 | } else if (port == HAL_SerialPort::HAL_SerialPort_MXP) { |
| 38 | return MxpResourceVISA; |
| 39 | } |
| 40 | |
| 41 | QueryHubPaths(status); |
| 42 | |
| 43 | // If paths are empty or status error, return error |
| 44 | if (*status != 0 || m_visaResource.empty() || m_osResource.empty() || |
| 45 | m_sortedHubPath.empty()) { |
| 46 | *status = HAL_SERIAL_PORT_NOT_FOUND; |
| 47 | return ""; |
| 48 | } |
| 49 | |
| 50 | int32_t visaIndex = GetIndexForPort(port, status); |
| 51 | if (visaIndex == -1) { |
| 52 | *status = HAL_SERIAL_PORT_NOT_FOUND; |
| 53 | return ""; |
| 54 | // Error |
| 55 | } else { |
| 56 | return m_visaResource[visaIndex].str(); |
| 57 | } |
| 58 | } |
| 59 | |
| 60 | std::string SerialHelper::GetOSSerialPortName(HAL_SerialPort port, |
| 61 | int32_t* status) { |
| 62 | if (port == HAL_SerialPort::HAL_SerialPort_Onboard) { |
| 63 | return OnboardResourceOS; |
| 64 | } else if (port == HAL_SerialPort::HAL_SerialPort_MXP) { |
| 65 | return MxpResourceOS; |
| 66 | } |
| 67 | |
| 68 | QueryHubPaths(status); |
| 69 | |
| 70 | // If paths are empty or status error, return error |
| 71 | if (*status != 0 || m_visaResource.empty() || m_osResource.empty() || |
| 72 | m_sortedHubPath.empty()) { |
| 73 | *status = HAL_SERIAL_PORT_NOT_FOUND; |
| 74 | return ""; |
| 75 | } |
| 76 | |
| 77 | int32_t osIndex = GetIndexForPort(port, status); |
| 78 | if (osIndex == -1) { |
| 79 | *status = HAL_SERIAL_PORT_NOT_FOUND; |
| 80 | return ""; |
| 81 | // Error |
| 82 | } else { |
| 83 | return m_osResource[osIndex].str(); |
| 84 | } |
| 85 | } |
| 86 | |
| 87 | std::vector<std::string> SerialHelper::GetVISASerialPortList(int32_t* status) { |
| 88 | std::vector<std::string> retVec; |
| 89 | |
| 90 | // Always add 2 onboard ports |
| 91 | retVec.emplace_back(OnboardResourceVISA); |
| 92 | retVec.emplace_back(MxpResourceVISA); |
| 93 | |
| 94 | QueryHubPaths(status); |
| 95 | |
| 96 | // If paths are empty or status error, return only onboard list |
| 97 | if (*status != 0 || m_visaResource.empty() || m_osResource.empty() || |
| 98 | m_sortedHubPath.empty()) { |
| 99 | *status = 0; |
| 100 | return retVec; |
| 101 | } |
| 102 | |
| 103 | for (auto& i : m_visaResource) { |
| 104 | retVec.emplace_back(i.str()); |
| 105 | } |
| 106 | |
| 107 | return retVec; |
| 108 | } |
| 109 | |
| 110 | std::vector<std::string> SerialHelper::GetOSSerialPortList(int32_t* status) { |
| 111 | std::vector<std::string> retVec; |
| 112 | |
| 113 | // Always add 2 onboard ports |
| 114 | retVec.emplace_back(OnboardResourceOS); |
| 115 | retVec.emplace_back(MxpResourceOS); |
| 116 | |
| 117 | QueryHubPaths(status); |
| 118 | |
| 119 | // If paths are empty or status error, return only onboard list |
| 120 | if (*status != 0 || m_visaResource.empty() || m_osResource.empty() || |
| 121 | m_sortedHubPath.empty()) { |
| 122 | *status = 0; |
| 123 | return retVec; |
| 124 | } |
| 125 | |
| 126 | for (auto& i : m_osResource) { |
| 127 | retVec.emplace_back(i.str()); |
| 128 | } |
| 129 | |
| 130 | return retVec; |
| 131 | } |
| 132 | |
| 133 | void SerialHelper::SortHubPathVector() { |
| 134 | m_sortedHubPath.clear(); |
| 135 | m_sortedHubPath = m_unsortedHubPath; |
| 136 | std::sort(m_sortedHubPath.begin(), m_sortedHubPath.end(), |
| 137 | [](const llvm::SmallVectorImpl<char>& lhs, |
| 138 | const llvm::SmallVectorImpl<char>& rhs) -> int { |
| 139 | llvm::StringRef lhsRef(lhs.begin(), lhs.size()); |
| 140 | llvm::StringRef rhsRef(rhs.begin(), rhs.size()); |
| 141 | return lhsRef.compare(rhsRef); |
| 142 | }); |
| 143 | } |
| 144 | |
| 145 | void SerialHelper::CoiteratedSort( |
| 146 | llvm::SmallVectorImpl<llvm::SmallString<16>>& vec) { |
| 147 | llvm::SmallVector<llvm::SmallString<16>, 4> sortedVec; |
| 148 | for (auto& str : m_sortedHubPath) { |
| 149 | for (size_t i = 0; i < m_unsortedHubPath.size(); i++) { |
| 150 | if (llvm::StringRef{m_unsortedHubPath[i].begin(), |
| 151 | m_unsortedHubPath[i].size()} |
| 152 | .equals(llvm::StringRef{str.begin(), str.size()})) { |
| 153 | sortedVec.push_back(vec[i]); |
| 154 | break; |
| 155 | } |
| 156 | } |
| 157 | } |
| 158 | vec = sortedVec; |
| 159 | } |
| 160 | |
| 161 | void SerialHelper::QueryHubPaths(int32_t* status) { |
| 162 | // VISA resource matching string |
| 163 | const char* str = "?*"; |
| 164 | // Items needed for VISA |
| 165 | ViUInt32 retCnt = 0; |
| 166 | ViFindList viList = 0; |
| 167 | ViChar desc[VI_FIND_BUFLEN]; |
| 168 | *status = viFindRsrc(m_resourceHandle, const_cast<char*>(str), &viList, |
| 169 | &retCnt, desc); |
| 170 | |
| 171 | if (*status < 0) { |
| 172 | // Handle the bad status elsewhere |
| 173 | // Note let positive statii (warnings) continue |
| 174 | goto done; |
| 175 | } |
| 176 | // Status might be positive, so reset it to 0 |
| 177 | *status = 0; |
| 178 | |
| 179 | // Storage buffers for Visa calls and system exec calls |
| 180 | char osName[256]; |
| 181 | char execBuffer[128]; |
| 182 | |
| 183 | // Loop through all returned VISA objects. |
| 184 | // Increment the internal VISA ptr every loop |
| 185 | for (size_t i = 0; i < retCnt; i++, viFindNext(viList, desc)) { |
| 186 | // Ignore any matches to the 2 onboard ports |
| 187 | if (std::strcmp(OnboardResourceVISA, desc) == 0 || |
| 188 | std::strcmp(MxpResourceVISA, desc) == 0) { |
| 189 | continue; |
| 190 | } |
| 191 | |
| 192 | // Open the resource, grab its interface name, and close it. |
| 193 | ViSession vSession; |
| 194 | *status = viOpen(m_resourceHandle, desc, VI_NULL, VI_NULL, &vSession); |
| 195 | if (*status < 0) goto done; |
| 196 | *status = 0; |
| 197 | |
| 198 | *status = viGetAttribute(vSession, VI_ATTR_INTF_INST_NAME, &osName); |
| 199 | // Ignore an error here, as we want to close the session on an error |
| 200 | // Use a seperate close variable so we can check |
| 201 | ViStatus closeStatus = viClose(vSession); |
| 202 | if (*status < 0) goto done; |
| 203 | if (closeStatus < 0) goto done; |
| 204 | *status = 0; |
| 205 | |
| 206 | // split until (/dev/ |
| 207 | llvm::StringRef devNameRef = llvm::StringRef{osName}.split("(/dev/").second; |
| 208 | // String not found, continue |
| 209 | if (devNameRef.equals("")) continue; |
| 210 | |
| 211 | // Split at ) |
| 212 | llvm::StringRef matchString = devNameRef.split(')').first; |
| 213 | if (matchString.equals(devNameRef)) continue; |
| 214 | |
| 215 | // Run find using pipe to get a list of system accessors |
| 216 | llvm::SmallString<128> val( |
| 217 | "sh -c \"find /sys/devices/soc0 | grep amba | grep usb | grep "); |
| 218 | val += matchString; |
| 219 | val += "\""; |
| 220 | |
| 221 | // Pipe code found on StackOverflow |
| 222 | // http://stackoverflow.com/questions/478898/how-to-execute-a-command-and-get-output-of-command-within-c-using-posix |
| 223 | |
| 224 | // Using std::string because this is guarenteed to be large |
| 225 | std::string output = ""; |
| 226 | |
| 227 | std::shared_ptr<FILE> pipe(popen(val.c_str(), "r"), pclose); |
| 228 | // Just check the next item on a pipe failure |
| 229 | if (!pipe) continue; |
| 230 | while (!feof(pipe.get())) { |
| 231 | if (std::fgets(execBuffer, 128, pipe.get()) != 0) output += execBuffer; |
| 232 | } |
| 233 | |
| 234 | if (!output.empty()) { |
| 235 | llvm::SmallVector<llvm::StringRef, 16> pathSplitVec; |
| 236 | // Split output by line, grab first line, and split it into |
| 237 | // individual directories |
| 238 | llvm::StringRef{output}.split('\n').first.split(pathSplitVec, '/', -1, |
| 239 | false); |
| 240 | |
| 241 | // Find each individual item index |
| 242 | |
| 243 | const char* usb1 = "usb1"; |
| 244 | const char* tty = "tty"; |
| 245 | |
| 246 | int findusb = -1; |
| 247 | int findtty = -1; |
| 248 | int findregex = -1; |
| 249 | for (size_t i = 0; i < pathSplitVec.size(); i++) { |
| 250 | if (findusb == -1 && pathSplitVec[i].equals(usb1)) { |
| 251 | findusb = i; |
| 252 | } |
| 253 | if (findtty == -1 && pathSplitVec[i].equals(tty)) { |
| 254 | findtty = i; |
| 255 | } |
| 256 | if (findregex == -1 && pathSplitVec[i].equals(matchString)) { |
| 257 | findregex = i; |
| 258 | } |
| 259 | } |
| 260 | |
| 261 | // Get the index for our device |
| 262 | int hubIndex = findtty; |
| 263 | if (findtty == -1) hubIndex = findregex; |
| 264 | |
| 265 | int devStart = findusb + 1; |
| 266 | |
| 267 | if (hubIndex < devStart) continue; |
| 268 | |
| 269 | // Add our devices to our list |
| 270 | m_unsortedHubPath.emplace_back( |
| 271 | llvm::StringRef{pathSplitVec[hubIndex - 2]}); |
| 272 | m_visaResource.emplace_back(desc); |
| 273 | m_osResource.emplace_back( |
| 274 | llvm::StringRef{osName}.split("(").second.split(")").first); |
| 275 | } |
| 276 | } |
| 277 | |
| 278 | SortHubPathVector(); |
| 279 | |
| 280 | CoiteratedSort(m_visaResource); |
| 281 | CoiteratedSort(m_osResource); |
| 282 | done: |
| 283 | viClose(viList); |
| 284 | } |
| 285 | |
| 286 | int32_t SerialHelper::GetIndexForPort(HAL_SerialPort port, int32_t* status) { |
| 287 | // Hold lock whenever we're using the names array |
| 288 | std::lock_guard<priority_mutex> lock(m_nameMutex); |
| 289 | |
| 290 | std::string portString = m_usbNames[port - 2]; |
| 291 | |
| 292 | llvm::SmallVector<int32_t, 4> indices; |
| 293 | |
| 294 | // If port has not been assigned, find the one to assign |
| 295 | if (portString.empty()) { |
| 296 | for (size_t i = 0; i < 2; i++) { |
| 297 | // Remove all used ports |
| 298 | auto idx = std::find(m_sortedHubPath.begin(), m_sortedHubPath.end(), |
| 299 | m_usbNames[i]); |
| 300 | if (idx != m_sortedHubPath.end()) { |
| 301 | // found |
| 302 | m_sortedHubPath.erase(idx); |
| 303 | } |
| 304 | if (m_usbNames[i] == "") { |
| 305 | indices.push_back(i); |
| 306 | } |
| 307 | } |
| 308 | |
| 309 | int32_t idx = -1; |
| 310 | for (size_t i = 0; i < indices.size(); i++) { |
| 311 | if (indices[i] == port - 2) { |
| 312 | idx = i; |
| 313 | break; |
| 314 | } |
| 315 | } |
| 316 | |
| 317 | if (idx == -1) { |
| 318 | *status = HAL_SERIAL_PORT_NOT_FOUND; |
| 319 | return -1; |
| 320 | } |
| 321 | |
| 322 | if (idx >= static_cast<int32_t>(m_sortedHubPath.size())) { |
| 323 | *status = HAL_SERIAL_PORT_NOT_FOUND; |
| 324 | return -1; |
| 325 | } |
| 326 | |
| 327 | portString = m_sortedHubPath[idx].str(); |
| 328 | m_usbNames[port - 2] = portString; |
| 329 | } |
| 330 | |
| 331 | int retIndex = -1; |
| 332 | |
| 333 | for (size_t i = 0; i < m_sortedHubPath.size(); i++) { |
| 334 | if (m_sortedHubPath[i].equals(portString)) { |
| 335 | retIndex = i; |
| 336 | break; |
| 337 | } |
| 338 | } |
| 339 | |
| 340 | return retIndex; |
| 341 | } |
| 342 | |
| 343 | } // namespace hal |