Qt編寫氣體安全管理系統4-通訊協議

1、前言

通訊協議解析是整個系統的核心靈魂,絕大部分人作軟硬件通訊開發,第一步估計就是寫demo將協議解析好,而後再慢慢寫整個界面和操做流程等,在工業控制領域,modbus協議應用仍是很是普遍的,這個系統用的也是modbus協議,採用此協議能夠方便的用一些第三方的組態軟件接入,兼容性更強,modbus只是個協議規約標準,至於使用串口仍是網絡仍是其餘,都由廠家自行具體實現,通常正常來講,使用modbus協議的系統支持255個設備在同一個網絡上進行通訊,並且大部分都會採用485半雙工的機制,挨個輪詢設備狀態,每次通訊鏈路上要麼在發送數據,要麼在接收數據,不能同時進行,這樣有個好處就是能夠傳輸的距離通常能夠到1200米,而若是是串口全雙工的話,基本上在100米就歇菜,具體還要看波特率,固然若是能夠用中轉設備進行轉發那除外。mysql

本項目即支持串口設備也支持網絡設備,在端口管理中添加好對應的通訊端口便可,modbus通訊協議的解析庫,有個第三方開源的libmodbus,跨平臺的,Qt從Qt5.8以來新增了總線serialbus庫,其中就包括了QModbusClient和QModbusServer來作modbus協議的解析,本人兩種都用過,大同小異,使用方法都差很少,整體上libmodbus要優於Qt自帶的modbus。可是爲了更大的靈活性,這個項目採用的本身程序解析協議數據來實現,這樣不用去糾結封裝的接口中每次發送幾個字節那些接口對應的含義等,並且本身解析也很方便,我寫過的全部的modbus通訊項目,都是採用的本身作協議解析。linux

皮膚開源:https://gitee.com/feiyangqingyun/QWidgetDemo https://github.com/feiyangqingyun/QWidgetDemo 文件名稱:styledemoc++

體驗地址:https://gitee.com/feiyangqingyun/QWidgetExe https://github.com/feiyangqingyun/QWidgetExe 文件名稱:bin_sams.zipgit

2、功能特色

  1. 採集數據端口,支持串口端口+網絡端口,串口支持自由設置串口號+波特率,網絡支持自由設置IP地址+通信端口,每一個端口支持採集週期,默認1秒鐘一個地址,支持設置通信超時次數,默認3次,支持最大重連時間,用於從新讀取離線的設備。
  2. 控制器信息,可以添加控制器名稱,選擇控制器地址+控制器型號,設置該控制器下面的探測器數量。
  3. 探測器信息,可以添加位號,可自由選擇探測器型號,氣體種類,氣體符號,高報值,低報值,緩衝值,清零值,是否啓用,報警聲音,背景地圖,存儲週期,數值換算小數點位數,報警延時時間,報警的類型(HH,LL,HL)等。
  4. 控制器型號+探測器型號+氣體種類+氣體符號,都可自由配置。
  5. 地圖支持導入和刪除,全部的探測器對應地圖位置可自由拖動保存。
  6. 端口信息+控制器信息+探測器信息,支持導入導出+導出到excel+打印。
  7. 運行記錄+報警記錄+用戶記錄,支持多條件組合查詢,好比時間段+控制器+探測器等,全部記錄支持導出到excel+打印。
  8. 導出到excel的記錄支持全部excel+wps等表格文件版本,不依賴excel等軟件。
  9. 可刪除指定時間範圍內的數據,支持自動清理早期數據,設置最大保存記錄數。
  10. 支持報警短信轉發,支持多個接收手機號碼,可設定發送間隔,好比即時發送或者6個小時發送一次全部的報警信息,短信內容過長,自動拆分多條短信。
  11. 支持報警郵件轉發,支持多個接收郵箱,可設定發送間隔,好比即時發送或者6個小時發送一次全部的報警信息,支持附件發送。
  12. 高報顏色+低報顏色+正常顏色+0值顏色+曲線背景+曲線顏色等,均可以自由選擇。
  13. 軟件的中文標題+英文標題+logo路徑+版權全部均可以自由設置。
  14. 提供開關設置開機運行+報警聲音+自動登陸+記住密碼等。
  15. 報警聲音可設置播放次數,界面提供17種皮膚文件選擇。
  16. 支持雲端數據同步,可設置雲端數據庫的信息,好比數據庫名稱,用戶名+密碼等。
  17. 支持網絡轉發和網絡接收,網絡接收開啓後,軟件從udp接收數據進行解析。網絡轉發支持多個目標IP,這樣就實現了本地採集的軟件,自由將數據轉到客戶端,隨時查看探測器數據。
  18. 自動記住用戶最後停留的界面+其餘信息,重啓後自動應用。
  19. 報警自動切換到對應的地圖,探測器按鈕閃爍。
  20. 雙擊探測器圖標,能夠進行回控。
  21. 支持用戶權限管理,管理員+操做員兩大類,用戶登陸+用戶退出,能夠記住密碼和自動登陸,超過三次報錯提示並關閉程序。
  22. 支持四種監控模式,設備面板監控+地圖監控+表格數據監控+曲線數據監控,可自由切換,四種同步應用。
  23. 支持報警繼電器聯動,一個位號能夠跨串口聯動多個模塊和繼電器號,支持多對多。
  24. 本地數據存儲支持sqlite+mysql,支持遠程數據同步到雲端數據庫。自動重連。
  25. 本地設備採集到的數據實時上傳到雲端,以便手機APP或者web等其餘方式提取。
  26. 支持兩種數據源,一種是串口和網絡經過協議採集設備數據,一種是數據庫採集。數據庫採集模式能夠做爲通用的系統使用。
  27. 自帶設備模擬工具,支持16個設備數據模擬,同時還帶數據庫數據模擬,以便在沒有設備的時候測試數據。
  28. 默認通訊協議採用modbus協議,後期增長mqtt等物聯網協議的支持,作成通用系統。
  29. 支持全部windows操做系統+linux操做系統和其餘操做系統。

3、效果圖

4、核心代碼

void DeviceClient::checkValue()
{
    if (!isOk) {
        return;
    }

    QMutexLocker locker(&mutex);
    readData();

    //至少要多少個字節,保證下面取數據不出錯
    int size = buffer.size();
    if (size < 5) {
        return;
    }

    //01 03 08 00 00 00 00 00 00 00 00 95 D7
    //01 03 08 00 14 03 12 00 00 00 00 79 E6

    //取出首字節,判斷是否爲當前地址集合中的地址
    quint8 addr = buffer.at(0);
    quint8 cmd = buffer.at(1);
    quint8 len = buffer.at(2);

    //若是是錯誤碼則直接解析錯誤信息
    QList<quint8> cmds;
    cmds << 0x03 << 0x04 << 0x06;
    if (!cmds.contains(cmd)) {
        emit receiveError(portName, addr, QString("數據出錯: %1").arg(QUIHelper::byteArrayToHexStr(buffer)));
        buffer.clear();
        return;
    }

    //若是數據過長則丟棄當前數據包,否則一旦產生了錯誤的數據會一直累積
    if (size > 517) {
        emit receiveError(portName, addr, QString("數據出錯: %1").arg(QUIHelper::byteArrayToHexStr(buffer)));
        buffer.clear();
        return;
    }

    //後面的數據長度必須大於等於長度數據位表示的長度
    if ((cmd == 0x03 || cmd == 0x04 || cmd == 0x06) && size < len + 5) {
        emit receiveError(portName, addr, QString("數據不全,等待完整數據再解析: %1").arg(QUIHelper::byteArrayToHexStr(buffer)));
        return;
    }

    //放在這裏發出去數據是準確的完整的
    emit receiveData(portName, addr, buffer);

    //過濾不存在的地址,防止索引越界
    int index = addrs.indexOf(addr);
    if (index < 0) {
        emit receiveError(portName, addr, "地址出錯: 當前地址不在設定的地址集合中");
        buffer.clear();
        return;
    }

    //來過消息的設備,立馬更新最後的消息時間,以及判斷設備上線
    times[index] = QDateTime::currentDateTime();
    if (!onlines.at(index)) {
        onlines[index] = true;
        emit receiveOnline(portName, addr, true);
        emit receiveInfo(portName, addr, "設備上線");
    }

    //根據不一樣的cmd+不一樣的命令類型 取出對應的數據內容
    if (cmd == 0x03) {
        QString info;
        if (currentType == "查詢濃度值") {
            QList<quint16> values;
            for (int i = 3; i < size - 2; i = i + 2) {
                values << (float)QUIHelper::byteToUShort(buffer.mid(i, 2));
            }

            QStringList list;
            foreach (quint16 value, values) {
                list << QString::number(value);
            }

            info = QString("%1返回: %2").arg(currentType).arg(list.join(" "));
            emit receiveValue(portName, addr, values);
        } else if (currentType == "查詢4路AD值") {
            //01 03 08 02 07 08 0C 00 00 00 00 73 87
            QList<quint16> values;
            for (int i = 3; i < size - 2; i = i + 2) {
                values << (float)QUIHelper::byteToUShort(buffer.mid(i, 2));
            }

            QStringList list;
            foreach (quint16 value, values) {
                list << QString::number(value);
            }

            info = QString("%1返回: %2").arg(currentType).arg(list.join(" "));
            emit receiveValueAD4(portName, addr, values);
        } else if (currentType == "查詢報警標誌") {
            //01 03 08 00 00 00 12 07 13 00 12 5D 68

        } else if (currentType == "查詢標定日期") {
            //01 03 04 00 12 07 13 19 CB
            int year = QString("20%1").arg(QString::number(buffer.at(4), todate)).toInt();
            int month = QString::number(buffer.at(5), todate).toInt();
            int day = QString::number(buffer.at(6), todate).toInt();
            info = QString("%1返回: %2年%3月%4日").arg(currentType).arg(year).arg(month).arg(day);
            emit receiveDate(portName, addr, year, month, day);
        } else if (currentType == "查詢出廠日期") {
            //01 03 04 00 12 07 13 19 CB
            int year = QString("20%1").arg(QString::number(buffer.at(4), todate)).toInt();
            int month = QString::number(buffer.at(5), todate).toInt();
            int day = QString::number(buffer.at(6), todate).toInt();
            info = QString("%1返回: %2年%3月%4日").arg(currentType).arg(year).arg(month).arg(day);
            emit receiveDateInit(portName, addr, year, month, day);
        }

        //發送對應的文字解析
        if (!info.isEmpty()) {
            emit receiveInfo(portName, addr, info);
        }
    } else if (cmd == 0x04) {

    } else if (cmd == 0x06) {

    }

    //從新賦值
    buffer.clear();
}
相關文章
相關標籤/搜索