| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348 |
- #include "SerialPortWatcher.h"
- #include <QDebug>
- #include <QFileInfo>
- #include <QSet>
- #include <QTimer>
- #ifdef Q_OS_LINUX
- #include "devices/platform/linux/UdevMonitor.h"
- #endif
- namespace devices::serial
- {
- namespace
- {
- constexpr int kFallbackPollIntervalMs = 60 * 1000;
- quint16 parseHexQuint16(const QVariant &value)
- {
- bool ok = false;
- const QString stringValue = value.toString();
- if (stringValue.isEmpty())
- {
- return 0;
- }
- const quint16 parsedValue = stringValue.toUShort(&ok, 16);
- return ok ? parsedValue : 0;
- }
- QString portNameFromSystemLocation(const QString &systemLocation)
- {
- if (systemLocation.isEmpty())
- {
- return {};
- }
- const QFileInfo fileInfo(systemLocation);
- return fileInfo.fileName();
- }
- QVariantMap toVariantMap(const SerialPortInfo &info)
- {
- QVariantMap map;
- map.insert(QStringLiteral("portName"), info.portName);
- map.insert(QStringLiteral("systemLocation"), info.systemLocation);
- map.insert(QStringLiteral("description"), info.description);
- map.insert(QStringLiteral("manufacturer"), info.manufacturer);
- map.insert(QStringLiteral("serialNumber"), info.serialNumber);
- map.insert(QStringLiteral("vendorId"), info.vendorId);
- map.insert(QStringLiteral("productId"), info.productId);
- return map;
- }
- } // namespace
- SerialPortWatcher::SerialPortWatcher(QObject *parent)
- : DeviceWatcherBase(parent)
- {
- qRegisterMetaType<SerialPortInfo>("devices::serial::SerialPortInfo");
- }
- SerialPortWatcher::~SerialPortWatcher()
- {
- stopWatching();
- }
- QList<SerialPortInfo> SerialPortWatcher::knownPorts() const
- {
- return m_knownPorts.values();
- }
- bool SerialPortWatcher::doStart()
- {
- #ifdef Q_OS_LINUX
- startUdevMonitor();
- #endif // Q_OS_LINUX
- if (!m_pollTimer)
- {
- m_pollTimer = new QTimer(this);
- connect(m_pollTimer, &QTimer::timeout, this, &SerialPortWatcher::refreshPorts);
- }
- m_pollTimer->setInterval(kFallbackPollIntervalMs);
- if (!m_pollTimer->isActive())
- {
- m_pollTimer->start();
- }
- refreshPorts();
- return true;
- }
- void SerialPortWatcher::doStop()
- {
- #ifdef Q_OS_LINUX
- stopUdevMonitor();
- #endif
- if (m_pollTimer)
- {
- m_pollTimer->stop();
- }
- m_knownPorts.clear();
- }
- void SerialPortWatcher::emitPortArrived(const SerialPortInfo &info)
- {
- emit serialPortArrived(info);
- emitDeviceArrived(info.portName, toVariantMap(info));
- }
- void SerialPortWatcher::emitPortRemoved(const SerialPortInfo &info)
- {
- emit serialPortRemoved(info);
- emitDeviceRemoved(info.portName, toVariantMap(info));
- }
- void SerialPortWatcher::refreshPorts()
- {
- const QList<QSerialPortInfo> currentInfos = QSerialPortInfo::availablePorts();
- QSet<QString> currentKeys;
- for (const QSerialPortInfo &info : currentInfos)
- {
- const SerialPortInfo serialInfo = toSerialPortInfo(info);
- const QString key = portIdentifier(serialInfo);
- if (key.isEmpty())
- {
- continue;
- }
- currentKeys.insert(key);
- const bool alreadyKnown = m_knownPorts.contains(key);
- m_knownPorts.insert(key, serialInfo);
- if (!alreadyKnown)
- {
- emitPortArrived(serialInfo);
- }
- }
- const QList<QString> knownKeys = m_knownPorts.keys();
- for (const QString &knownKey : knownKeys)
- {
- if (!currentKeys.contains(knownKey))
- {
- const SerialPortInfo removedInfo = m_knownPorts.take(knownKey);
- SerialPortInfo infoToEmit = removedInfo;
- if (!infoToEmit.isValid())
- {
- infoToEmit.portName = portNameFromSystemLocation(knownKey);
- infoToEmit.systemLocation = knownKey;
- }
- emitPortRemoved(infoToEmit);
- }
- }
- }
- SerialPortInfo SerialPortWatcher::toSerialPortInfo(const QSerialPortInfo &info) const
- {
- SerialPortInfo serialInfo;
- serialInfo.portName = info.portName();
- serialInfo.systemLocation = info.systemLocation();
- serialInfo.description = info.description();
- serialInfo.manufacturer = info.manufacturer();
- serialInfo.serialNumber = info.serialNumber();
- serialInfo.vendorId = info.hasVendorIdentifier() ? info.vendorIdentifier() : 0;
- serialInfo.productId = info.hasProductIdentifier() ? info.productIdentifier() : 0;
- return serialInfo;
- }
- #ifdef Q_OS_LINUX
- bool SerialPortWatcher::startUdevMonitor()
- {
- if (m_udevMonitor)
- {
- return true;
- }
- m_udevMonitor = std::make_unique<devices::platform::linux_os::UdevMonitor>(this);
- if (!m_udevMonitor->start(QStringLiteral("tty")))
- {
- m_udevMonitor.reset();
- return false;
- }
- connect(m_udevMonitor.get(),
- &devices::platform::linux_os::UdevMonitor::deviceAdded,
- this,
- &SerialPortWatcher::handleUdevAdd);
- connect(m_udevMonitor.get(),
- &devices::platform::linux_os::UdevMonitor::deviceRemoved,
- this,
- &SerialPortWatcher::handleUdevRemove);
- return true;
- }
- void SerialPortWatcher::stopUdevMonitor()
- {
- if (!m_udevMonitor)
- {
- return;
- }
- disconnect(m_udevMonitor.get(),
- &devices::platform::linux_os::UdevMonitor::deviceAdded,
- this,
- &SerialPortWatcher::handleUdevAdd);
- disconnect(m_udevMonitor.get(),
- &devices::platform::linux_os::UdevMonitor::deviceRemoved,
- this,
- &SerialPortWatcher::handleUdevRemove);
- m_udevMonitor->stop();
- m_udevMonitor.reset();
- }
- void SerialPortWatcher::handleUdevAdd(const QVariantMap &properties)
- {
- const SerialPortInfo info = resolvePortFromProperties(properties);
- if (!info.isValid())
- {
- refreshPorts();
- return;
- }
- const QString key = portIdentifier(info);
- if (key.isEmpty())
- {
- refreshPorts();
- return;
- }
- const bool alreadyKnown = m_knownPorts.contains(key);
- m_knownPorts.insert(key, info);
- if (!alreadyKnown)
- {
- emitPortArrived(info);
- }
- }
- void SerialPortWatcher::handleUdevRemove(const QVariantMap &properties)
- {
- const QString devnode = properties.value(QStringLiteral("devnode")).toString();
- const QString portName = properties.value(QStringLiteral("portName")).toString();
- SerialPortInfo info = takeKnownPort(portName, devnode);
- if (!info.isValid())
- {
- info.portName = !portName.isEmpty() ? portName : portNameFromSystemLocation(devnode);
- info.systemLocation = devnode;
- }
- emitPortRemoved(info);
- }
- #endif
- QString SerialPortWatcher::portIdentifier(const SerialPortInfo &info) const
- {
- return portIdentifier(info.portName, info.systemLocation);
- }
- QString SerialPortWatcher::portIdentifier(const QString &portName, const QString &systemLocation) const
- {
- return !systemLocation.isEmpty() ? systemLocation : portName;
- }
- SerialPortInfo SerialPortWatcher::resolvePortFromProperties(const QVariantMap &properties) const
- {
- QString devnode = properties.value(QStringLiteral("devnode")).toString();
- QString portName = properties.value(QStringLiteral("portName")).toString();
- if (portName.isEmpty())
- {
- portName = portNameFromSystemLocation(devnode);
- }
- const QList<QSerialPortInfo> availablePorts = QSerialPortInfo::availablePorts();
- for (const QSerialPortInfo &info : availablePorts)
- {
- if ((!devnode.isEmpty() && info.systemLocation() == devnode) ||
- (!portName.isEmpty() && info.portName() == portName))
- {
- return toSerialPortInfo(info);
- }
- }
- SerialPortInfo fallback;
- fallback.portName = portName;
- fallback.systemLocation = devnode;
- fallback.description = properties.value(QStringLiteral("description")).toString();
- fallback.serialNumber = properties.value(QStringLiteral("serialNumber")).toString();
- fallback.vendorId = parseHexQuint16(properties.value(QStringLiteral("vendorId")));
- fallback.productId = parseHexQuint16(properties.value(QStringLiteral("productId")));
- return fallback;
- }
- SerialPortInfo SerialPortWatcher::takeKnownPort(const QString &portName, const QString &systemLocation)
- {
- const QString key = portIdentifier(portName, systemLocation);
- if (!key.isEmpty())
- {
- auto it = m_knownPorts.find(key);
- if (it != m_knownPorts.end())
- {
- SerialPortInfo info = it.value();
- m_knownPorts.erase(it);
- return info;
- }
- }
- if (!systemLocation.isEmpty())
- {
- for (auto it = m_knownPorts.begin(); it != m_knownPorts.end(); ++it)
- {
- if (it.value().systemLocation == systemLocation)
- {
- SerialPortInfo info = it.value();
- m_knownPorts.erase(it);
- return info;
- }
- }
- }
- if (!portName.isEmpty())
- {
- for (auto it = m_knownPorts.begin(); it != m_knownPorts.end(); ++it)
- {
- if (it.value().portName == portName)
- {
- SerialPortInfo info = it.value();
- m_knownPorts.erase(it);
- return info;
- }
- }
- }
- SerialPortInfo fallback;
- fallback.portName = portName;
- fallback.systemLocation = systemLocation;
- return fallback;
- }
- } // namespace devices::serial
|