SerialPortWatcher.cpp 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  1. #include "SerialPortWatcher.h"
  2. #include <QDebug>
  3. #include <QFileInfo>
  4. #include <QSet>
  5. #include <QTimer>
  6. #ifdef Q_OS_LINUX
  7. #include "devices/platform/linux/UdevMonitor.h"
  8. #endif
  9. namespace devices::serial
  10. {
  11. namespace
  12. {
  13. constexpr int kFallbackPollIntervalMs = 60 * 1000;
  14. quint16 parseHexQuint16(const QVariant &value)
  15. {
  16. bool ok = false;
  17. const QString stringValue = value.toString();
  18. if (stringValue.isEmpty())
  19. {
  20. return 0;
  21. }
  22. const quint16 parsedValue = stringValue.toUShort(&ok, 16);
  23. return ok ? parsedValue : 0;
  24. }
  25. QString portNameFromSystemLocation(const QString &systemLocation)
  26. {
  27. if (systemLocation.isEmpty())
  28. {
  29. return {};
  30. }
  31. const QFileInfo fileInfo(systemLocation);
  32. return fileInfo.fileName();
  33. }
  34. QVariantMap toVariantMap(const SerialPortInfo &info)
  35. {
  36. QVariantMap map;
  37. map.insert(QStringLiteral("portName"), info.portName);
  38. map.insert(QStringLiteral("systemLocation"), info.systemLocation);
  39. map.insert(QStringLiteral("description"), info.description);
  40. map.insert(QStringLiteral("manufacturer"), info.manufacturer);
  41. map.insert(QStringLiteral("serialNumber"), info.serialNumber);
  42. map.insert(QStringLiteral("vendorId"), info.vendorId);
  43. map.insert(QStringLiteral("productId"), info.productId);
  44. return map;
  45. }
  46. } // namespace
  47. SerialPortWatcher::SerialPortWatcher(QObject *parent)
  48. : DeviceWatcherBase(parent)
  49. {
  50. qRegisterMetaType<SerialPortInfo>("devices::serial::SerialPortInfo");
  51. }
  52. SerialPortWatcher::~SerialPortWatcher()
  53. {
  54. stopWatching();
  55. }
  56. QList<SerialPortInfo> SerialPortWatcher::knownPorts() const
  57. {
  58. return m_knownPorts.values();
  59. }
  60. bool SerialPortWatcher::doStart()
  61. {
  62. #ifdef Q_OS_LINUX
  63. startUdevMonitor();
  64. #endif // Q_OS_LINUX
  65. if (!m_pollTimer)
  66. {
  67. m_pollTimer = new QTimer(this);
  68. connect(m_pollTimer, &QTimer::timeout, this, &SerialPortWatcher::refreshPorts);
  69. }
  70. m_pollTimer->setInterval(kFallbackPollIntervalMs);
  71. if (!m_pollTimer->isActive())
  72. {
  73. m_pollTimer->start();
  74. }
  75. refreshPorts();
  76. return true;
  77. }
  78. void SerialPortWatcher::doStop()
  79. {
  80. #ifdef Q_OS_LINUX
  81. stopUdevMonitor();
  82. #endif
  83. if (m_pollTimer)
  84. {
  85. m_pollTimer->stop();
  86. }
  87. m_knownPorts.clear();
  88. }
  89. void SerialPortWatcher::emitPortArrived(const SerialPortInfo &info)
  90. {
  91. emit serialPortArrived(info);
  92. emitDeviceArrived(info.portName, toVariantMap(info));
  93. }
  94. void SerialPortWatcher::emitPortRemoved(const SerialPortInfo &info)
  95. {
  96. emit serialPortRemoved(info);
  97. emitDeviceRemoved(info.portName, toVariantMap(info));
  98. }
  99. void SerialPortWatcher::refreshPorts()
  100. {
  101. const QList<QSerialPortInfo> currentInfos = QSerialPortInfo::availablePorts();
  102. QSet<QString> currentKeys;
  103. for (const QSerialPortInfo &info : currentInfos)
  104. {
  105. const SerialPortInfo serialInfo = toSerialPortInfo(info);
  106. const QString key = portIdentifier(serialInfo);
  107. if (key.isEmpty())
  108. {
  109. continue;
  110. }
  111. currentKeys.insert(key);
  112. const bool alreadyKnown = m_knownPorts.contains(key);
  113. m_knownPorts.insert(key, serialInfo);
  114. if (!alreadyKnown)
  115. {
  116. emitPortArrived(serialInfo);
  117. }
  118. }
  119. const QList<QString> knownKeys = m_knownPorts.keys();
  120. for (const QString &knownKey : knownKeys)
  121. {
  122. if (!currentKeys.contains(knownKey))
  123. {
  124. const SerialPortInfo removedInfo = m_knownPorts.take(knownKey);
  125. SerialPortInfo infoToEmit = removedInfo;
  126. if (!infoToEmit.isValid())
  127. {
  128. infoToEmit.portName = portNameFromSystemLocation(knownKey);
  129. infoToEmit.systemLocation = knownKey;
  130. }
  131. emitPortRemoved(infoToEmit);
  132. }
  133. }
  134. }
  135. SerialPortInfo SerialPortWatcher::toSerialPortInfo(const QSerialPortInfo &info) const
  136. {
  137. SerialPortInfo serialInfo;
  138. serialInfo.portName = info.portName();
  139. serialInfo.systemLocation = info.systemLocation();
  140. serialInfo.description = info.description();
  141. serialInfo.manufacturer = info.manufacturer();
  142. serialInfo.serialNumber = info.serialNumber();
  143. serialInfo.vendorId = info.hasVendorIdentifier() ? info.vendorIdentifier() : 0;
  144. serialInfo.productId = info.hasProductIdentifier() ? info.productIdentifier() : 0;
  145. return serialInfo;
  146. }
  147. #ifdef Q_OS_LINUX
  148. bool SerialPortWatcher::startUdevMonitor()
  149. {
  150. if (m_udevMonitor)
  151. {
  152. return true;
  153. }
  154. m_udevMonitor = std::make_unique<devices::platform::linux_os::UdevMonitor>(this);
  155. if (!m_udevMonitor->start(QStringLiteral("tty")))
  156. {
  157. m_udevMonitor.reset();
  158. return false;
  159. }
  160. connect(m_udevMonitor.get(),
  161. &devices::platform::linux_os::UdevMonitor::deviceAdded,
  162. this,
  163. &SerialPortWatcher::handleUdevAdd);
  164. connect(m_udevMonitor.get(),
  165. &devices::platform::linux_os::UdevMonitor::deviceRemoved,
  166. this,
  167. &SerialPortWatcher::handleUdevRemove);
  168. return true;
  169. }
  170. void SerialPortWatcher::stopUdevMonitor()
  171. {
  172. if (!m_udevMonitor)
  173. {
  174. return;
  175. }
  176. disconnect(m_udevMonitor.get(),
  177. &devices::platform::linux_os::UdevMonitor::deviceAdded,
  178. this,
  179. &SerialPortWatcher::handleUdevAdd);
  180. disconnect(m_udevMonitor.get(),
  181. &devices::platform::linux_os::UdevMonitor::deviceRemoved,
  182. this,
  183. &SerialPortWatcher::handleUdevRemove);
  184. m_udevMonitor->stop();
  185. m_udevMonitor.reset();
  186. }
  187. void SerialPortWatcher::handleUdevAdd(const QVariantMap &properties)
  188. {
  189. const SerialPortInfo info = resolvePortFromProperties(properties);
  190. if (!info.isValid())
  191. {
  192. refreshPorts();
  193. return;
  194. }
  195. const QString key = portIdentifier(info);
  196. if (key.isEmpty())
  197. {
  198. refreshPorts();
  199. return;
  200. }
  201. const bool alreadyKnown = m_knownPorts.contains(key);
  202. m_knownPorts.insert(key, info);
  203. if (!alreadyKnown)
  204. {
  205. emitPortArrived(info);
  206. }
  207. }
  208. void SerialPortWatcher::handleUdevRemove(const QVariantMap &properties)
  209. {
  210. const QString devnode = properties.value(QStringLiteral("devnode")).toString();
  211. const QString portName = properties.value(QStringLiteral("portName")).toString();
  212. SerialPortInfo info = takeKnownPort(portName, devnode);
  213. if (!info.isValid())
  214. {
  215. info.portName = !portName.isEmpty() ? portName : portNameFromSystemLocation(devnode);
  216. info.systemLocation = devnode;
  217. }
  218. emitPortRemoved(info);
  219. }
  220. #endif
  221. QString SerialPortWatcher::portIdentifier(const SerialPortInfo &info) const
  222. {
  223. return portIdentifier(info.portName, info.systemLocation);
  224. }
  225. QString SerialPortWatcher::portIdentifier(const QString &portName, const QString &systemLocation) const
  226. {
  227. return !systemLocation.isEmpty() ? systemLocation : portName;
  228. }
  229. SerialPortInfo SerialPortWatcher::resolvePortFromProperties(const QVariantMap &properties) const
  230. {
  231. QString devnode = properties.value(QStringLiteral("devnode")).toString();
  232. QString portName = properties.value(QStringLiteral("portName")).toString();
  233. if (portName.isEmpty())
  234. {
  235. portName = portNameFromSystemLocation(devnode);
  236. }
  237. const QList<QSerialPortInfo> availablePorts = QSerialPortInfo::availablePorts();
  238. for (const QSerialPortInfo &info : availablePorts)
  239. {
  240. if ((!devnode.isEmpty() && info.systemLocation() == devnode) ||
  241. (!portName.isEmpty() && info.portName() == portName))
  242. {
  243. return toSerialPortInfo(info);
  244. }
  245. }
  246. SerialPortInfo fallback;
  247. fallback.portName = portName;
  248. fallback.systemLocation = devnode;
  249. fallback.description = properties.value(QStringLiteral("description")).toString();
  250. fallback.serialNumber = properties.value(QStringLiteral("serialNumber")).toString();
  251. fallback.vendorId = parseHexQuint16(properties.value(QStringLiteral("vendorId")));
  252. fallback.productId = parseHexQuint16(properties.value(QStringLiteral("productId")));
  253. return fallback;
  254. }
  255. SerialPortInfo SerialPortWatcher::takeKnownPort(const QString &portName, const QString &systemLocation)
  256. {
  257. const QString key = portIdentifier(portName, systemLocation);
  258. if (!key.isEmpty())
  259. {
  260. auto it = m_knownPorts.find(key);
  261. if (it != m_knownPorts.end())
  262. {
  263. SerialPortInfo info = it.value();
  264. m_knownPorts.erase(it);
  265. return info;
  266. }
  267. }
  268. if (!systemLocation.isEmpty())
  269. {
  270. for (auto it = m_knownPorts.begin(); it != m_knownPorts.end(); ++it)
  271. {
  272. if (it.value().systemLocation == systemLocation)
  273. {
  274. SerialPortInfo info = it.value();
  275. m_knownPorts.erase(it);
  276. return info;
  277. }
  278. }
  279. }
  280. if (!portName.isEmpty())
  281. {
  282. for (auto it = m_knownPorts.begin(); it != m_knownPorts.end(); ++it)
  283. {
  284. if (it.value().portName == portName)
  285. {
  286. SerialPortInfo info = it.value();
  287. m_knownPorts.erase(it);
  288. return info;
  289. }
  290. }
  291. }
  292. SerialPortInfo fallback;
  293. fallback.portName = portName;
  294. fallback.systemLocation = systemLocation;
  295. return fallback;
  296. }
  297. } // namespace devices::serial