Browse Source

轮训查设备

flower_linux 1 month ago
parent
commit
5bb09cd2ff

+ 181 - 2
src/serial_manager/serial_manager.cpp

@@ -7,6 +7,7 @@
 #include <QJsonDocument>
 #include <QJsonArray>
 #include <QJsonObject>
+#include <algorithm>
 
 SerialManager::SerialManager(SoftBusCore *busCore, QObject *parent)
     : QObject(parent),
@@ -186,6 +187,14 @@ QStringList SerialManager::getAvailablePorts() const
 
 bool SerialManager::isRealSerialPort(const QSerialPortInfo &info) const
 {
+    const QString portName = info.portName();
+
+    // 如果是我们已经追踪或正在使用的端口,无论是否能立即打开,都视为真实串口
+    if (m_openedPorts.contains(portName) || m_lastDiscoveredPorts.contains(portName))
+    {
+        return true;
+    }
+
     // 1. 尝试打开串口进行验证
     QSerialPort testPort;
     testPort.setPort(info);
@@ -223,8 +232,12 @@ bool SerialManager::isRealSerialPort(const QSerialPortInfo &info) const
     // 3. 检查是否有物理设备属性
     if (info.vendorIdentifier() == 0 && info.productIdentifier() == 0)
     {
-        // 没有硬件ID的可能是虚拟端口
-        return false;
+        // 没有硬件ID的可能是虚拟端口,但也可能是被占用的真实设备
+        // 缓和判定:仅当描述同样为空时才判定为虚拟端口
+        if (info.description().isEmpty() && info.manufacturer().isEmpty())
+        {
+            return false;
+        }
     }
 
     // 4. 检查串口是否在系统设备列表中
@@ -281,12 +294,19 @@ void SerialManager::discoverSerialPorts()
     QSet<QString> currentPorts;
     foreach (const QSerialPortInfo &info, QSerialPortInfo::availablePorts())
     {
+        if (!isRealSerialPort(info))
+        {
+            // qDebug() << "Skip virtual serial port:" << info.portName() << "-" << info.description();
+            continue;
+        }
+
         QString portName = info.portName();
         currentPorts.insert(portName);
 
         // 如果是新发现的端口
         if (!m_lastDiscoveredPorts.contains(portName))
         {
+            registerDiscoveredPort(info);
             emit portDiscovered(portName, info.description());
             qDebug() << "New port discovered:" << portName << "-" << info.description();
         }
@@ -301,6 +321,10 @@ void SerialManager::discoverSerialPorts()
         {
             closeSerialPort(portName);
         }
+        if (m_busCore)
+        {
+            m_busCore->markDeviceOfflineByPort(portName);
+        }
         emit portRemoved(portName);
         qDebug() << "Port removed:" << portName;
     }
@@ -375,6 +399,161 @@ void SerialManager::initThreads()
     }
 }
 
+void SerialManager::registerDiscoveredPort(const QSerialPortInfo &info)
+{
+    if (!m_busCore)
+    {
+        qDebug() << "Bus core is not set; skip registering port" << info.portName();
+        return;
+    }
+
+    if (!isRealSerialPort(info))
+    {
+        qDebug() << "Skip registering non-real serial port" << info.portName();
+        return;
+    }
+
+    const QString portName = info.portName();
+    DeviceInfo existingDevice = m_busCore->getDeviceInfoByPortname(portName);
+    if (existingDevice.id != 0)
+    {
+        // 已经注册的设备,如果描述或属性发生变化,更新一次
+        QString desiredName = info.description().isEmpty()
+                                  ? QStringLiteral("串口设备: %1").arg(portName)
+                                  : info.description();
+
+        QJsonObject updatedProperties = existingDevice.properties;
+        auto mergeProperty = [&updatedProperties](const QString &key, const QString &value) {
+            if (value.isEmpty())
+            {
+                return false;
+            }
+            if (!updatedProperties.contains(key) || updatedProperties.value(key).toString() != value)
+            {
+                updatedProperties.insert(key, value);
+                return true;
+            }
+            return false;
+        };
+
+        bool propertyChanged = false;
+        propertyChanged |= mergeProperty(QStringLiteral("description"), info.description());
+        propertyChanged |= mergeProperty(QStringLiteral("manufacturer"), info.manufacturer());
+        propertyChanged |= mergeProperty(QStringLiteral("serialNumber"), info.serialNumber());
+        propertyChanged |= mergeProperty(QStringLiteral("systemLocation"), info.systemLocation());
+
+        // vendorIdentifier / productIdentifier 可能为0,但仍然写入,便于调试
+        const QString vendorId = info.hasVendorIdentifier()
+                                     ? QString::number(info.vendorIdentifier(), 16)
+                                     : QString();
+        const QString productId = info.hasProductIdentifier()
+                                      ? QString::number(info.productIdentifier(), 16)
+                                      : QString();
+        propertyChanged |= mergeProperty(QStringLiteral("vendorId"), vendorId);
+        propertyChanged |= mergeProperty(QStringLiteral("productId"), productId);
+
+        bool nameChanged = existingDevice.name != desiredName;
+
+        const bool needFullUpdate = nameChanged || propertyChanged;
+        if (needFullUpdate)
+        {
+            existingDevice.name = desiredName;
+            existingDevice.properties = updatedProperties;
+            existingDevice.status = QStringLiteral("online");
+            existingDevice.isActive = true;
+            existingDevice.lastSeen = QDateTime::currentDateTimeUtc();
+            m_busCore->updateDevice(existingDevice);
+            qDebug() << "Updated existing serial device registration for port" << portName;
+        }
+
+        if (!needFullUpdate)
+        {
+            m_busCore->updateDeviceStatus(existingDevice.id, QStringLiteral("online"), true,
+                                          QDateTime::currentDateTimeUtc());
+        }
+        return;
+    }
+
+    DeviceInfo device;
+    device.id = generateNextDeviceId();
+    if (device.id <= 0)
+    {
+        qWarning() << "Failed to allocate device ID for port" << portName;
+        return;
+    }
+
+    device.portname = portName;
+    device.type = QStringLiteral("serial");
+    device.protocol = QStringLiteral("modbus");
+    device.protocol_detail = QStringLiteral("modbus-rtu");
+    device.address = 0;
+    device.status = QStringLiteral("online");
+    device.isActive = true;
+    device.lastSeen = QDateTime::currentDateTimeUtc();
+
+    if (info.description().isEmpty())
+    {
+        device.name = QStringLiteral("串口设备: %1").arg(portName);
+    }
+    else if (info.description().contains(portName))
+    {
+        device.name = info.description();
+    }
+    else
+    {
+        device.name = QStringLiteral("%1 (%2)").arg(info.description(), portName);
+    }
+
+    QJsonObject properties;
+    if (!info.description().isEmpty())
+    {
+        properties.insert(QStringLiteral("description"), info.description());
+    }
+    if (!info.manufacturer().isEmpty())
+    {
+        properties.insert(QStringLiteral("manufacturer"), info.manufacturer());
+    }
+    if (!info.serialNumber().isEmpty())
+    {
+        properties.insert(QStringLiteral("serialNumber"), info.serialNumber());
+    }
+    if (!info.systemLocation().isEmpty())
+    {
+        properties.insert(QStringLiteral("systemLocation"), info.systemLocation());
+    }
+    if (info.hasVendorIdentifier())
+    {
+        properties.insert(QStringLiteral("vendorId"), QString::number(info.vendorIdentifier(), 16));
+    }
+    if (info.hasProductIdentifier())
+    {
+        properties.insert(QStringLiteral("productId"), QString::number(info.productIdentifier(), 16));
+    }
+
+    device.properties = properties;
+
+    m_busCore->registerDevice(device);
+    m_busCore->updateDeviceStatus(device.id, QStringLiteral("online"), true,
+                                  device.lastSeen);
+    qDebug() << "Registered serial device in bus core for port" << portName << "with id" << device.id;
+}
+
+int SerialManager::generateNextDeviceId() const
+{
+    if (!m_busCore)
+    {
+        return -1;
+    }
+
+    const QList<DeviceInfo> devices = m_busCore->getAllDevices();
+    int maxId = 0;
+    for (const DeviceInfo &device : devices)
+    {
+        maxId = std::max(maxId, device.id);
+    }
+    return maxId + 1;
+}
+
 void SerialManager::cleanupThreads()
 {
     // 停止并清理存储线程

+ 4 - 0
src/serial_manager/serial_manager.h

@@ -105,6 +105,10 @@ private:
     void initThreads();
     // 清理线程
     void cleanupThreads();
+    // 为新发现的串口注册软总线设备
+    void registerDiscoveredPort(const QSerialPortInfo &info);
+    // 计算下一个可用的设备ID
+    int generateNextDeviceId() const;
 
 private:
     // 软总线核心

+ 236 - 24
src/soft_bus_core/soft_bus_core.cpp

@@ -102,29 +102,132 @@ void SoftBusCore::registerDevice(const DeviceInfo &device) {
         return;
     }
 
-    m_devices.insert(device.id, device);
+    DeviceInfo normalizedDevice = device;
+    if (normalizedDevice.status.isEmpty()) {
+        normalizedDevice.status = QStringLiteral("online");
+    }
+    normalizedDevice.isActive = true;
+    if (!normalizedDevice.lastSeen.isValid()) {
+        normalizedDevice.lastSeen = QDateTime::currentDateTimeUtc();
+    }
+
+    // 如果未显式指定名称,则尝试沿用传入数据
+    if (normalizedDevice.name.isEmpty()) {
+        normalizedDevice.name = device.name;
+    }
+
+    int existingDeviceId = -1;
+    DeviceInfo persistedDevice;
+    const bool hasPort =
+        !normalizedDevice.portname.isEmpty();
+    if (hasPort) {
+        if (m_portnameToId.contains(normalizedDevice.portname)) {
+            existingDeviceId = m_portnameToId.value(normalizedDevice.portname);
+            if (m_devices.contains(existingDeviceId)) {
+                persistedDevice = m_devices.value(existingDeviceId);
+            } else {
+                // 内存缺失该设备,移除映射,稍后尝试从数据库查询
+                m_portnameToId.remove(normalizedDevice.portname);
+                existingDeviceId = -1;
+            }
+        }
+
+        if (existingDeviceId <= 0 && m_dbInitialized) {
+            if (fetchPersistedDeviceByPort(normalizedDevice.portname,
+                                           persistedDevice)) {
+                existingDeviceId = persistedDevice.id;
+                if (existingDeviceId > 0) {
+                    m_devices.insert(existingDeviceId, persistedDevice);
+                    m_portnameToId[normalizedDevice.portname] = existingDeviceId;
+                }
+            }
+        }
+    }
+
+    if (existingDeviceId > 0) {
+        DeviceInfo mergedDevice = normalizedDevice;
+        mergedDevice.id = existingDeviceId;
+        updateDevice(mergedDevice);
+        return;
+    }
+
+    m_devices.insert(normalizedDevice.id, normalizedDevice);
 
     // 如果设备有端口名,建立映射关系
-    if (!device.portname.isEmpty()) {
-        m_portnameToId[device.portname] = device.id;
+    if (!normalizedDevice.portname.isEmpty()) {
+        m_portnameToId[normalizedDevice.portname] = normalizedDevice.id;
     }
 
     if (m_dbInitialized) {
         // 先同步设备信息,再确保端口对应的原始/转化数据表存在
-        upsertDeviceRecord(device);
-        if (!device.portname.isEmpty()) {
-            if (!ensurePerPortRawTable(device.portname)) {
+        upsertDeviceRecord(normalizedDevice, normalizedDevice.lastSeen);
+        if (!normalizedDevice.portname.isEmpty()) {
+            if (!ensurePerPortRawTable(normalizedDevice.portname)) {
                 qCritical() << "Failed to ensure raw data table for port"
-                            << device.portname;
+                            << normalizedDevice.portname;
             }
-            if (!ensurePerPortTransformedTable(device.portname)) {
+            if (!ensurePerPortTransformedTable(normalizedDevice.portname)) {
                 qCritical() << "Failed to ensure transformed data table for port"
-                            << device.portname;
+                            << normalizedDevice.portname;
             }
         }
     }
 
-    emit deviceRegistered(device);
+    emit deviceRegistered(normalizedDevice);
+}
+
+bool SoftBusCore::fetchPersistedDeviceByPort(const QString &portName,
+                                             DeviceInfo &device) {
+    if (!m_dbInitialized || !m_db.isValid() || !m_db.isOpen() ||
+        portName.isEmpty()) {
+        return false;
+    }
+
+    QSqlQuery query(m_db);
+    query.prepare(QStringLiteral(
+        "SELECT device_id, port_name, name, device_type, address, protocol, "
+        "protocol_detail, properties, status, is_active, last_seen "
+        "FROM devices WHERE port_name = :port_name LIMIT 1"));
+    query.bindValue(QStringLiteral(":port_name"), portName);
+    if (!query.exec()) {
+        qCritical() << "Failed to query persisted device for port" << portName
+                    << ":" << query.lastError().text();
+        return false;
+    }
+
+    if (!query.next()) {
+        return false;
+    }
+
+    DeviceInfo info;
+    info.id = query.value(0).toInt();
+    info.portname = query.value(1).toString();
+    info.name = query.value(2).toString();
+    info.type = query.value(3).toString();
+    info.address = query.value(4).toInt();
+    info.protocol = query.value(5).toString();
+    info.protocol_detail = query.value(6).toString();
+
+    const QString propertiesString = query.value(7).toString();
+    if (!propertiesString.isEmpty()) {
+        const QJsonDocument doc =
+            QJsonDocument::fromJson(propertiesString.toUtf8());
+        if (doc.isObject()) {
+            info.properties = doc.object();
+        }
+    }
+
+    info.status = query.value(8).toString();
+    info.isActive = query.value(9).toBool();
+    info.lastSeen = query.value(10).toDateTime();
+
+    if (info.status.isEmpty()) {
+        info.status =
+            info.isActive ? QStringLiteral("online") : QStringLiteral("offline");
+    }
+
+    device = info;
+    return true;
 }
 
 // 更新设备
@@ -151,34 +254,134 @@ void SoftBusCore::updateDevice(const DeviceInfo &device) {
 
     // 如果端口名发生变化,更新映射
     DeviceInfo oldDevice = m_devices[device.id];
-    if (oldDevice.portname != device.portname) {
+    DeviceInfo updatedDevice = device;
+    if (oldDevice.portname != updatedDevice.portname) {
         if (!oldDevice.portname.isEmpty()) {
             m_portnameToId.remove(oldDevice.portname);
         }
-        if (!device.portname.isEmpty()) {
-            m_portnameToId[device.portname] = device.id;
+        if (!updatedDevice.portname.isEmpty()) {
+            m_portnameToId[updatedDevice.portname] = updatedDevice.id;
         }
     }
 
-    m_devices[device.id] = device;
+    if (updatedDevice.status.isEmpty()) {
+        updatedDevice.status =
+            oldDevice.status.isEmpty() ? QStringLiteral("online") : oldDevice.status;
+    }
+    if (!updatedDevice.lastSeen.isValid() && oldDevice.lastSeen.isValid()) {
+        updatedDevice.lastSeen = oldDevice.lastSeen;
+    }
+    // 如果调用方未显式修改活跃状态,则沿用旧值
+    if (device.isActive == oldDevice.isActive) {
+        updatedDevice.isActive = oldDevice.isActive;
+    }
+
+    m_devices[device.id] = updatedDevice;
 
     if (m_dbInitialized) {
-        upsertDeviceRecord(device);
-        if (!device.portname.isEmpty()) {
-            if (!ensurePerPortRawTable(device.portname)) {
+        upsertDeviceRecord(updatedDevice, updatedDevice.lastSeen);
+        if (!updatedDevice.portname.isEmpty()) {
+            if (!ensurePerPortRawTable(updatedDevice.portname)) {
                 qCritical() << "Failed to ensure raw data table for port"
-                            << device.portname;
+                            << updatedDevice.portname;
             }
-            if (!ensurePerPortTransformedTable(device.portname)) {
+            if (!ensurePerPortTransformedTable(updatedDevice.portname)) {
                 qCritical() << "Failed to ensure transformed data table for port"
-                            << device.portname;
+                            << updatedDevice.portname;
             }
         }
     }
 
+    emit deviceUpdate(updatedDevice);
+}
+
+void SoftBusCore::markDeviceOffline(int deviceId) {
+    if (deviceId <= 0) {
+        qWarning() << "Invalid device id when marking offline";
+        return;
+    }
+
+    const bool tracked = m_devices.contains(deviceId);
+    if (!tracked) {
+        qWarning() << "Device" << deviceId
+                   << "not tracked in memory when marking offline";
+        if (m_dbInitialized) {
+            markDeviceInactive(deviceId);
+        }
+        return;
+    }
+
+    updateDeviceStatus(deviceId, QStringLiteral("offline"), false,
+                       QDateTime::currentDateTimeUtc());
+}
+
+void SoftBusCore::markDeviceOfflineByPort(const QString &portname) {
+    if (portname.isEmpty()) {
+        qWarning() << "Empty port name when marking device offline";
+        return;
+    }
+
+    if (!m_portnameToId.contains(portname)) {
+        qWarning() << "No registered device found for port" << portname
+                   << "when marking offline";
+        return;
+    }
+
+    const int deviceId = m_portnameToId.value(portname);
+    markDeviceOffline(deviceId);
+}
+
+void SoftBusCore::updateDeviceStatus(int deviceId, const QString &status,
+                                     bool isActive, const QDateTime &lastSeen) {
+    if (deviceId <= 0) {
+        qWarning() << "Invalid device id when updating status";
+        return;
+    }
+
+    if (!m_devices.contains(deviceId)) {
+        qWarning() << "Device" << deviceId
+                   << "not tracked in memory when updating status";
+        return;
+    }
+
+    DeviceInfo device = m_devices.value(deviceId);
+    if (!status.isEmpty()) {
+        device.status = status;
+    }
+    device.isActive = isActive;
+    if (lastSeen.isValid()) {
+        device.lastSeen = lastSeen;
+    } else if (!device.lastSeen.isValid()) {
+        device.lastSeen = QDateTime::currentDateTimeUtc();
+    }
+
+    m_devices[deviceId] = device;
+
+    if (m_dbInitialized) {
+        upsertDeviceRecord(device, device.lastSeen);
+    }
+
     emit deviceUpdate(device);
 }
 
+void SoftBusCore::updateDeviceStatusByPort(const QString &portname,
+                                           const QString &status, bool isActive,
+                                           const QDateTime &lastSeen) {
+    if (portname.isEmpty()) {
+        qWarning() << "Empty port name when updating device status";
+        return;
+    }
+
+    if (!m_portnameToId.contains(portname)) {
+        qWarning() << "No registered device found for port" << portname
+                   << "when updating status";
+        return;
+    }
+
+    const int deviceId = m_portnameToId.value(portname);
+    updateDeviceStatus(deviceId, status, isActive, lastSeen);
+}
+
 // 注销设备
 /* 
     参数:
@@ -1162,10 +1365,18 @@ void SoftBusCore::upsertDeviceRecord(const DeviceInfo &device,
     query.bindValue(QStringLiteral(":protocol_detail"), device.protocol_detail);
     query.bindValue(QStringLiteral(":properties"),
                     toJsonVariant(device.properties));
-    query.bindValue(QStringLiteral(":status"), QStringLiteral("online"));
-    query.bindValue(QStringLiteral(":is_active"), true);
-    if (lastSeen.isValid()) {
-        query.bindValue(QStringLiteral(":last_seen"), lastSeen);
+    const QString statusValue = device.status.isEmpty()
+                                    ? QStringLiteral("online")
+                                    : device.status;
+    query.bindValue(QStringLiteral(":status"), statusValue);
+    query.bindValue(QStringLiteral(":is_active"), device.isActive);
+
+    QDateTime effectiveLastSeen = lastSeen;
+    if (!effectiveLastSeen.isValid() && device.lastSeen.isValid()) {
+        effectiveLastSeen = device.lastSeen;
+    }
+    if (effectiveLastSeen.isValid()) {
+        query.bindValue(QStringLiteral(":last_seen"), effectiveLastSeen);
     } else {
         query.bindValue(QStringLiteral(":last_seen"), QVariant(QVariant::DateTime));
     }
@@ -1284,3 +1495,4 @@ int SoftBusCore::loadEnvOrDefault(const char *envVar, int defaultValue) const {
     const int parsed = value.toInt(&ok);
     return ok ? parsed : defaultValue;
 }
+

+ 21 - 4
src/soft_bus_core/soft_bus_core.h

@@ -21,15 +21,23 @@ struct DeviceInfo {
   QString
       protocol_detail; // 协议详情: "modbus-rtu", "modbus-tcp", "modbus-ascii"
   QJsonObject properties; // 其他属性
+  QString status;         // 当前状态: "online", "offline", "error" 等
+  bool isActive;          // 是否活跃
+  QDateTime lastSeen;     // 最近一次活跃时间
 
-  DeviceInfo() : id(0) {}
+  DeviceInfo()
+      : id(0), status(QStringLiteral("online")), isActive(true) {}
   DeviceInfo(const int &id, const QString &type, const int &address = 0,
              const QString &protocol = "", const QString &protocol_detail = "",
              const QString &portname = "",
-             const QJsonObject &properties = QJsonObject())
-      : id(id), portname(portname), type(type), address(address),
+             const QJsonObject &properties = QJsonObject(),
+             const QString &status = QStringLiteral("online"),
+             bool isActive = true,
+             const QDateTime &lastSeen = QDateTime())
+      : id(id), portname(portname), name(), type(type), address(address),
         protocol(protocol), protocol_detail(protocol_detail),
-        properties(properties) {}
+        properties(properties), status(status), isActive(isActive),
+        lastSeen(lastSeen) {}
 };
 
 // 软总线消息结构
@@ -61,6 +69,14 @@ public:
   void registerDevice(const DeviceInfo &device);
   void unregisterDevice(int deviceId);
   void updateDevice(const DeviceInfo &device);
+  void markDeviceOffline(int deviceId);
+  void markDeviceOfflineByPort(const QString &portname);
+  void updateDeviceStatus(int deviceId, const QString &status,
+                          bool isActive = true,
+                          const QDateTime &lastSeen = QDateTime());
+  void updateDeviceStatusByPort(const QString &portname,
+                                const QString &status, bool isActive = true,
+                                const QDateTime &lastSeen = QDateTime());
   DeviceInfo getDeviceInfo(int deviceId) const;
   DeviceInfo getDeviceInfoByPortname(const QString &portname) const;
   QList<DeviceInfo> getAllDevices() const;
@@ -109,6 +125,7 @@ private:
   bool ensurePerPortTransformedTable(const QString &portName);
   void upsertDeviceRecord(const DeviceInfo &device,
                           const QDateTime &lastSeen = QDateTime());
+  bool fetchPersistedDeviceByPort(const QString &portName, DeviceInfo &device);
   void markDeviceInactive(int deviceId);
   QString sanitizeIdentifier(const QString &identifier) const;
   QString buildRawTableName(const QString &portName) const;