一、前言
在物聯網管理平臺的實際現場應用過程中,遇到過大大小小几十個改進的需求點,這些需求點都是實際用戶提出來的,一方面爲了方便用戶使用提高用戶體驗,一方面爲了提升整體的整個系統的完整性,甚至有些需求說的不好聽一點就是造假,比如硬件設備精度不夠,會短暫的在某些值附近飄動,但是客戶端採集到數據後,如果該值剛好在報警值附近飄動,不能說是報警,因爲也有可能是干擾引起的飄動,這種嚴格意義上說又不算是報警,只有持續超過了報警值,纔算是真正的報警,所以需要設計一個報警延時參數,超過了延時時間還處於報警則認爲是報警。
存儲週期很好理解,就是多久存儲一次這個設備的記錄值,有些設備很重要,可以將這個存儲週期設置的小一點,比如5s就存儲一個值,有些不大重要的,可以設置一個很大的值,這樣可以因地制宜,省下不少的存儲空間。報警類型也是一個很好的參數規則,對於我們常見的規則就是大於上限值屬於高報,低於下限值屬於低報,比如溫度就屬於這個情況。而有些氣體是高於最小值屬於低報,高於最大值屬於高報,相當於一定要低於最低值纔是正常的,有毒氣體一般都屬於這個類型。有些特定環境又是要求氣體的濃度超過最大值才正常比如氧氣,要求超過最大值纔是正常,最大值和最小值中間是低報,小於最小值是高報,因爲氧氣太少了屬於最緊急的事件。
字段說明
- 編 號:節點的編號,從1開始。
- 位 號:探測器的位置編號,用於唯一標識一個探測器。
- 控 制 器:對應掛載的主設備名稱。
- 探 測 器:探測器的名稱,方便記憶,可以填寫地理位置。
- 地 址:探測器對應在控制器的編號索引。
- 型 號:探測器的型號,從下拉框選擇。
- 氣體種類:探測器對應採集的氣體的種類。
- 氣體型號:探測器對應採集氣體的型號。
- 上 限 值:報警的上限值。
- 下 限 值:報警的下限值
- 最 大 值:最大的警戒值,超過該值則顯示爲該值。
- 消 零:最小的警戒值,小於該值則顯示0,大於顯示真實值。
- 量 程:假設量程0.25則 實際數=模擬量/4000x量程 模擬量就是採集的值。
- 狀 態:默認啓用,當某個探測器未接時候可以選擇禁用。
- 聲 音:報警後對應的聲音文件。
- 地 圖:探測器所位於的地圖文件。
- 存 儲:探測器記錄存儲的週期,單位分鐘。即隔多久存儲一次記錄到本地。
- 小 數 點:計算解析數據的數據位對應的小數點位數。
- 報警延時:報警後,延時多久處理,以便過濾數據抖動偏差造成的誤報。默認0。
- 報警類型:HH LL HL。
- X坐 標:探測器位於地圖上的X座標。
- Y坐 標:探測器位於地圖上的Y座標。
二、功能特點
2.1 軟件模塊
- 設備監控模塊,包括數據監控(表格形式展示)、設備面板(面板形式展示)、地圖監控(地圖形式展示)、曲線監控(曲線形式展示)。
- 數據查詢模塊,包括報警記錄、運行記錄、操作記錄。
- 系統設置模塊,包括基本設置、端口管理、控制器管理、探測器管理、報警聯動、類型設置等。
- 其他設置模塊,包括用戶管理、地圖管理、位置調整、組態設計、設備調試等。
2.2 基礎功能
- 設備數據採集,支持串口、網絡,串口可設置串口號、波特率,網絡可設置IP地址、通訊端口。
- 每個端口支持採集週期時間,默認1秒鐘一個設備。
- 支持設置通訊超時次數,默認3次。
- 支持最大重連時間,用於重新讀取離線的設備。
- 控制器信息,能夠添加控制器名稱,選擇控制器地址、控制器型號,設置該控制器下面的探測器數量。
- 探測器信息,能夠添加位號、探測器型號、氣體種類、氣體符號、高報值、低報值、緩衝值、清零值、是否啓用、報警聲音、背景地圖、存儲週期、數值換算小數點位數、報警延時時間、報警的類型(HH,LL,HL)等。
- 類型管理可配置控制器型號、探測器型號、氣體種類、氣體符號等。
- 地圖支持導入和刪除,所有的探測器在地圖上的位置可自由拖動保存。
- 端口信息、控制器信息、探測器信息、類型信息、用戶信息等,都支持導入、導出、導出到excel、打印。
- 運行記錄、報警記錄、操作記錄,都支持多條件組合查詢,比如時間段、控制器、探測器等,所有記錄支持導出到excel/pdf和打印。
- 運行記錄、報警記錄、操作記錄都可刪除指定時間範圍內的數據。
- 系統設置可選擇對應表最大保存記錄數,自動清理早期數據,留出足夠的空間存儲重要的數據。
- 報警短信轉發,支持多個接收手機號碼,可設定發送間隔,比如即時發送或者6個小時發送一次所有的報警信息,短信內容過長,自動拆分多條短信。
- 報警郵件轉發,支持多個接收郵箱,可設定發送間隔,比如即時發送或者6個小時發送一次所有的報警信息,支持附件發送。
- 設置軟件的中文標題、英文標題、logo路徑、版權所有等。
- 開關設置開機運行、報警聲音、自動登錄、記住密碼等。
- 報警聲音可設置播放次數,界面風格樣式提供18套皮膚文件選擇。
- 用戶管理,包括用戶權限配置,不同用戶可以有不同模塊的權限。
- 用戶登錄和用戶退出,可以記住密碼和自動登錄,超過三次報錯提示並關閉程序。
- 四種監控模式,設備面板監控、地圖監控、表格數據監控、曲線數據監控,可自由切換,四種模式下都實時展示採集到的數據,報警閃爍等。
- 報警繼電器聯動,一個位號可以跨串口聯動多個模塊和繼電器號,支持多對多。
2.3 特色功能
- 通信協議支持modbus_com、modbus_tcp_rtu,後期拓展mqtt等協議。
- 數據源除了真實的硬件設備採集,還可選數據庫採集,這樣用戶可以安排其他程序員比如java程序員將前端採集好的數據放到數據庫,本系統直接從數據庫採集即可。數據庫採集模式可以作爲通用的系統使用,更適合多人多系統協作。
- 智能跳過超時的設備,加快對在線設備的採集速度,當設備數量很多的時候尤其有用。
- 對智能跳過的超時的設備,在設定的重連時間自動採集一次,以便探測設備是否又重新上線。
- 每個探測器可控是否啓用,不啓用則不會採集,也不會在界面顯示,相當於運行階段臨時關閉。
- 探測器可設置緩衝值和報警延時時間,在該值附近波動產生的報警,不計入報警,只有持續處於報警值且超過報警延時時間纔算真正報警,這樣可以規避很多波動導致的誤報。
- 探測器可設置存儲週期,按照設定的時間來存儲一條運行記錄,可以按照重要程度對重要性高的設定存儲週期短一些,不重要的設定大一些,這樣可以節省不少的存儲空間,也保證了重要的數據及時存儲。
- 探測器可設置清零值,在一些高精度高靈敏的設備可能出廠的時候默認值未必是0,需要設定清零值來表示初始值。
- 探測器可設置小數點,用於計算後的真實數據控制小數點點位顯示,相當於除以10、除以100、除以1000,這樣大部分的探測器數據直接通過小數點位設置控制真實換算後的值,極個別的需要特殊轉換的可以在通信協議中約定。
- 探測器報警的類型支持多種,有些設備是高於某個值高報,低於某個值低報,而有些設備是在最小值最大值範圍內是高報,低於最小值低報,高於最大值正常。這樣可以分情況處理,涵蓋各種報警類型。
- 原創數據導入、導出、打印機制,跨平臺不依賴任何組件,瞬間導出數據。
- 導出到excel的記錄支持所有excel、wps等表格文件版本,不依賴excel等軟件。
- 高報顏色、低報顏色、正常顏色、默認值顏色等,都可以自由設置。
- 支持雲端數據同步,將本地採集到的數據實時同步到雲端。
- 支持網絡轉發和網絡接收,網絡接收開啓後,軟件從udp接收數據進行解析。網絡轉發支持多個目標IP,這樣就實現了本地採集的軟件,自由將數據轉到客戶端,隨時查看採集到的數據。
- 自動記住用戶最後停留的界面以及其他配置信息,重啓後自動應用。
- 報警自動切換到對應的地圖,探測器按鈕閃爍,表格數據對應顏色顯示。
- 雙擊探測器圖標,彈出對應探測器詳細信息,可以根據需要定製回控操作。
- 數據庫支持多種,包括sqlite、mysql、sqlserver、postgresql、oracle、人大金倉等。
- 本地設備採集到的數據實時上傳到雲端,以便手機APP或者web等其他方式提取。
- 自帶設備模擬工具,支持不同型號的多個設備數據模擬,同時還帶數據庫數據模擬,以便在沒有設備的時候測試數據。
- 標準modbus協議,各種控制器類型、探測器類型、種類、符號等全部自定義,非常靈活和強大,通信協議示例數據非常完整,通用各種modbus協議系統,適用於各種應用場景接入。
- 同時集成了串口通信、網絡通信、數據庫通信、數據導入導出打印、通信協議解析、界面UI、全局換膚等衆多組件和知識點,非常適合新手入門和進階。
- 支持xp、win7、win10、、win11、linux、mac、各種國產系統(UOS、中標麒麟、銀河麒麟等)、嵌入式linux等系統。
- 註釋完整,項目結構清晰,超級詳細完整的使用開發手冊,精確到每個代碼文件的功能說明,不斷持續迭代版本。
三、體驗地址
- 國內站點:https://gitee.com/feiyangqingyun
- 國際站點:https://github.com/feiyangqingyun
- 個人主頁:https://blog.csdn.net/feiyangqingyun
- 知乎主頁:https://www.zhihu.com/people/feiyangqingyun
- 產品主頁:https://blog.csdn.net/feiyangqingyun/article/details/97565652
- 在線文檔:https://feiyangqingyun.gitee.io/qwidgetdemo/iotsystem/
- 體驗地址:https://pan.baidu.com/s/1ZxG-oyUKe286LPMPxOrO2A 提取碼:o05q 文件名:bin_iotsystem.zip。
- 文章導航:https://qtchina.blog.csdn.net/article/details/121330922
四、效果圖
五、相關代碼
void DeviceServer::doReceiveValue(const QString &portName, quint8 addr, const QList<quint16> &values)
{
//找到設備名稱
QString deviceName = DbQuery::getDeviceName(portName, addr);
//找到當前索引位置的設備地址對應探測器的最小寄存器地址
//如果讀取的起始寄存器地址是5則回來的數據位第一個是寄存器地址5的數據,後面連續
quint16 nodeMinAddr = DbQuery::getNodeMinAddr(portName, addr);
//根據不同的探測器對應的小數點,換算值
QList<float> datas;
foreach (quint16 value, values) {
datas << value;
}
for (int i = 0; i < DbData::NodeInfo_Count; ++i) {
if (DbData::NodeInfo_DeviceName.at(i) == deviceName) {
//有時候可能出現總共添加了8個探測器但是真實讀到4個探測器的情況
int startIndex = DbData::NodeInfo_NodeAddr.at(i) - nodeMinAddr - 1;
if (startIndex >= datas.count()) {
continue;
}
QString positionID = DbData::NodeInfo_PositionID.at(i);
float nodeMax = DbData::NodeInfo_NodeMax.at(i);
float nodeMin = DbData::NodeInfo_NodeMin.at(i);
float nodeRange = DbData::NodeInfo_NodeRange.at(i);
int dotCount = DbData::NodeInfo_DotCount.at(i);
//目前收到的值需要經過幾層過濾計算纔是真實的值
//第一層是小數點(默認0),比如收到的值是1000,如果小數點設定的1則真實的是100
//第二層是量程(默認0),比如收到998,如果設定的量程0.25則運算後 998/4000*0.25=0.062375
//第三層是最大值(默認1000),假設設定的最大值1000,如果收到的值>1000則取1000,因爲某些設備損壞或者誤報採集到的是一個很大的不準確的值
//第四層是清零值(默認0),假設設定的是50,則低於50都認爲是0,因爲某些設備損壞或者誤報採集到的是一個很小的不準確的值
//根據設定的小數點來重新計算真實的值
float nodeValue = (float)values.at(startIndex);
if (dotCount > 0) {
nodeValue = nodeValue / qPow(10, dotCount);
}
//4218按照新的規則計算值 實際數=模擬量/4000*量程 模擬量就是採集的值
if (nodeRange > 0) {
nodeValue = (float)values.at(startIndex) / 4000 * nodeRange;
}
//如果收到的值大於最大值則取最大值作爲當前的值
nodeValue = nodeValue > nodeMax ? nodeMax : nodeValue;
//如果設置了消零閥值,在未達到消零閥值時顯示都是零,只有超過消零閥值纔是顯示真實值
if (nodeRange == 0) {
nodeValue = nodeValue < nodeMin ? 0 : nodeValue;
}
//精度過濾,避免精度過大顯示太長
nodeValue = QString::number(nodeValue, 'f', AppConfig::Precision).toFloat();
//找到當前探測器處理探測器報警
//如果當前值小於最小值而且當前不處於下限報警則觸發報警
//如果當前值大於最大值而且當前不處於上限報警則觸發報警
//0-低報 1-低報恢復 2-高報 3-高報恢復
quint8 nodeStatus = 100;
//根據設定的不同的報警類型處理,假定上限值100,下限值25
//HH表示超過25是低報,超過100是高報
//HL表示低於25是低報,超過100是高報
//LL表示低於25是高報,低於100是低報
QString alarmType = DbData::NodeInfo_AlarmType.at(i);
if (alarmType == "HH") {
doAlarmHH(nodeStatus, positionID, nodeValue, i);
} else if (alarmType == "HL") {
doAlarmHL(nodeStatus, positionID, nodeValue, i);
} else if (alarmType == "LL") {
doAlarmLL(nodeStatus, positionID, nodeValue, i);
}
//處理報警
//qDebug() << TIMEMS << positionID << nodeStatus << nodeValue;
doAlarm(nodeStatus, positionID, nodeValue, i);
//沒有變動的數據則無需處理,不然界面上一下子這麼多數據刷新可能導致高CPU,一樣的數據也沒有必要
if (DbData::NodeInfo_NodeValue.at(i) != nodeValue) {
QString nodeSign = DbData::NodeInfo_NodeSign.at(i);
DeviceHelper::deviceValue(positionID, nodeValue, nodeSign);
DbData::NodeInfo_NodeValue[i] = nodeValue;
}
//雲端數據同步
if (AppConfig::UseNetDb) {
QString sql = QString("update NodeData set NodeValue='%1',SaveTime='%2' where PositionID='%3'").arg(nodeValue).arg(DATETIME).arg(positionID);
DbData::DbNet->append(sql);
}
datas[startIndex] = nodeValue;
}
}
emit receiveValue(portName, addr, datas);
}