找回密码
 立即注册
收起左侧

Qt编写物联网管理平台48-特色功能设计

0
回复
165
查看
[复制链接]
累计签到:7 天
连续签到:1 天
来源: 2022-7-28 09:19:00 显示全部楼层 |阅读模式


## 一、前言
在物联网管理平台的实际现场应用过程中,遇到过大大小小几十个改进的需求点,这些需求点都是实际用户提出来的,一方面为了方便用户使用提高用户体验,一方面为了提升整体的整个系统的完整性,甚至有些需求说的不好听一点就是造假,比如硬件设备精度不够,会短暂的在某些值附近飘动,但是客户端采集到数据后,如果该值刚好在报警值附近飘动,不能说是报警,因为也有可能是干扰引起的飘动,这种严格意义上说又不算是报警,只有持续超过了报警值,才算是真正的报警,所以需要设计一个报警延时参数,超过了延时时间还处于报警则认为是报警。


存储周期很好理解,就是多久存储一次这个设备的记录值,有些设备很重要,可以将这个存储周期设置的小一点,比如5s就存储一个值,有些不大重要的,可以设置一个很大的值,这样可以因地制宜,省下不少的存储空间。报警类型也是一个很好的参数规则,对于我们常见的规则就是大于上限值属于高报,低于下限值属于低报,比如温度就属于这个情况。而有些气体是高于最小值属于低报,高于最大值属于高报,相当于一定要低于最低值才是正常的,有毒气体一般都属于这个类型。有些特定环境又是要求气体的浓度超过最大值才正常比如氧气,要求超过最大值才是正常,最大值和最小值中间是低报,小于最小值是高报,因为氧气太少了属于最紧急的事件。


**字段说明**


1. 编    号:节点的编号,从1开始。
2. 位    号:探测器的位置编号,用于唯一标识一个探测器。
3. 控 制 器:对应挂载的主设备名称。
4. 探 测 器:探测器的名称,方便记忆,可以填写地理位置。
5. 地    址:探测器对应在控制器的编号索引。
6. 型    号:探测器的型号,从下拉框选择。
7. 气体种类:探测器对应采集的气体的种类。
8. 气体型号:探测器对应采集气体的型号。
9. 上 限 值:报警的上限值。
10. 下 限 值:报警的下限值
11. 最 大 值:最大的警戒值,超过该值则显示为该值。
12. 消    零:最小的警戒值,小于该值则显示0,大于显示真实值。
13. 量    程:假设量程0.25则 实际数=模拟量/4000x量程 模拟量就是采集的值。
14. 状    态:默认启用,当某个探测器未接时候可以选择禁用。
15. 声    音:报警后对应的声音文件。
16. 地    图:探测器所位于的地图文件。
17. 存    储:探测器记录存储的周期,单位分钟。即隔多久存储一次记录到本地。
18. 小 数 点:计算解析数据的数据位对应的小数点位数。
19. 报警延时:报警后,延时多久处理,以便过滤数据抖动偏差造成的误报。默认0。
20. 报警类型:HH LL HL。
21. X坐 标:探测器位于地图上的X坐标。
22. Y坐 标:探测器位于地图上的Y坐标。


## 二、功能特点
### 2.1 软件模块
1. 设备监控模块,包括数据监控(表格形式展示)、设备面板(面板形式展示)、地图监控(地图形式展示)、曲线监控(曲线形式展示)。
2. 数据查询模块,包括报警记录、运行记录、操作记录。
3. 系统设置模块,包括基本设置、端口管理、控制器管理、探测器管理、报警联动、类型设置等。
4. 其他设置模块,包括用户管理、地图管理、位置调整、组态设计、设备调试等。


### 2.2 基础功能
1. 设备数据采集,支持串口、网络,串口可设置串口号、波特率,网络可设置IP地址、通讯端口。
2. 每个端口支持采集周期时间,默认1秒钟一个设备。
3. 支持设置通讯超时次数,默认3次。
4. 支持最大重连时间,用于重新读取离线的设备。
5. 控制器信息,能够添加控制器名称,选择控制器地址、控制器型号,设置该控制器下面的探测器数量。
6. 探测器信息,能够添加位号、探测器型号、气体种类、气体符号、高报值、低报值、缓冲值、清零值、是否启用、报警声音、背景地图、存储周期、数值换算小数点位数、报警延时时间、报警的类型(HH,LL,HL)等。
7. 类型管理可配置控制器型号、探测器型号、气体种类、气体符号等。
8. 地图支持导入和删除,所有的探测器在地图上的位置可自由拖动保存。
9. 端口信息、控制器信息、探测器信息、类型信息、用户信息等,都支持导入、导出、导出到excel、打印。
10. 运行记录、报警记录、操作记录,都支持多条件组合查询,比如时间段、控制器、探测器等,所有记录支持导出到excel/pdf和打印。
11. 运行记录、报警记录、操作记录都可删除指定时间范围内的数据。
12. 系统设置可选择对应表最大保存记录数,自动清理早期数据,留出足够的空间存储重要的数据。
13. 报警短信转发,支持多个接收手机号码,可设定发送间隔,比如即时发送或者6个小时发送一次所有的报警信息,短信内容过长,自动拆分多条短信。
14. 报警邮件转发,支持多个接收邮箱,可设定发送间隔,比如即时发送或者6个小时发送一次所有的报警信息,支持附件发送。
15. 设置软件的中文标题、英文标题、logo路径、版权所有等。
16. 开关设置开机运行、报警声音、自动登录、记住密码等。
17. 报警声音可设置播放次数,界面风格样式提供18套皮肤文件选择。
18. 用户管理,包括用户权限配置,不同用户可以有不同模块的权限。
19. 用户登录和用户退出,可以记住密码和自动登录,超过三次报错提示并关闭程序。
20. 四种监控模式,设备面板监控、地图监控、表格数据监控、曲线数据监控,可自由切换,四种模式下都实时展示采集到的数据,报警闪烁等。
21. 报警继电器联动,一个位号可以跨串口联动多个模块和继电器号,支持多对多。


### 2.3 特色功能
1. 通信协议支持modbus_com、modbus_tcp_rtu,后期拓展mqtt等协议。
2. 数据源除了真实的硬件设备采集,还可选数据库采集,这样用户可以安排其他程序员比如java程序员将前端采集好的数据放到数据库,本系统直接从数据库采集即可。数据库采集模式可以作为通用的系统使用,更适合多人多系统协作。
3. 智能跳过超时的设备,加快对在线设备的采集速度,当设备数量很多的时候尤其有用。
4. 对智能跳过的超时的设备,在设定的重连时间自动采集一次,以便探测设备是否又重新上线。
5. 每个探测器可控是否启用,不启用则不会采集,也不会在界面显示,相当于运行阶段临时关闭。
6. 探测器可设置缓冲值和报警延时时间,在该值附近波动产生的报警,不计入报警,只有持续处于报警值且超过报警延时时间才算真正报警,这样可以规避很多波动导致的误报。
7. 探测器可设置存储周期,按照设定的时间来存储一条运行记录,可以按照重要程度对重要性高的设定存储周期短一些,不重要的设定大一些,这样可以节省不少的存储空间,也保证了重要的数据及时存储。
8. 探测器可设置清零值,在一些高精度高灵敏的设备可能出厂的时候默认值未必是0,需要设定清零值来表示初始值。
9. 探测器可设置小数点,用于计算后的真实数据控制小数点点位显示,相当于除以10、除以100、除以1000,这样大部分的探测器数据直接通过小数点位设置控制真实换算后的值,极个别的需要特殊转换的可以在通信协议中约定。
10. 探测器报警的类型支持多种,有些设备是高于某个值高报,低于某个值低报,而有些设备是在最小值最大值范围内是高报,低于最小值低报,高于最大值正常。这样可以分情况处理,涵盖各种报警类型。
11. 原创数据导入、导出、打印机制,跨平台不依赖任何组件,瞬间导出数据。
12. 导出到excel的记录支持所有excel、wps等表格文件版本,不依赖excel等软件。
13. 高报颜色、低报颜色、正常颜色、默认值颜色等,都可以自由设置。
14. 支持云端数据同步,将本地采集到的数据实时同步到云端。
15. 支持网络转发和网络接收,网络接收开启后,软件从udp接收数据进行解析。网络转发支持多个目标IP,这样就实现了本地采集的软件,自由将数据转到客户端,随时查看采集到的数据。
16. 自动记住用户最后停留的界面以及其他配置信息,重启后自动应用。
17. 报警自动切换到对应的地图,探测器按钮闪烁,表格数据对应颜色显示。
18. 双击探测器图标,弹出对应探测器详细信息,可以根据需要定制回控操作。
19. 数据库支持多种,包括sqlite、mysql、sqlserver、postgresql、oracle、人大金仓等。
20. 本地设备采集到的数据实时上传到云端,以便手机APP或者web等其他方式提取。
21. 自带设备模拟工具,支持不同型号的多个设备数据模拟,同时还带数据库数据模拟,以便在没有设备的时候测试数据。
22. 标准modbus协议,各种控制器类型、探测器类型、种类、符号等全部自定义,非常灵活和强大,通信协议示例数据非常完整,通用各种modbus协议系统,适用于各种应用场景接入。
23. 同时集成了串口通信、网络通信、数据库通信、数据导入导出打印、通信协议解析、界面UI、全局换肤等众多组件和知识点,非常适合新手入门和进阶。
24. 支持xp、win7、win10、、win11、linux、mac、各种国产系统(UOS、中标麒麟、银河麒麟等)、嵌入式linux等系统。
25. 注释完整,项目结构清晰,超级详细完整的使用开发手册,精确到每个代码文件的功能说明,不断持续迭代版本。


## 三、体验地址
1. 国内站点:[https://gitee.com/feiyangqingyun](https://gitee.com/feiyangqingyun)
2. 国际站点:[https://github.com/feiyangqingyun](https://github.com/feiyangqingyun)
3. 个人主页:[https://blog.csdn.net/feiyangqingyun](https://blog.csdn.net/feiyangqingyun)
4. 知乎主页:[https://www.zhihu.com/people/feiyangqingyun](https://www.zhihu.com/people/feiyangqingyun)
5. 产品主页:[https://blog.csdn.net/feiyangqingyun/article/details/97565652](https://blog.csdn.net/feiyangqingyun/article/details/97565652)
6. 在线文档:[https://feiyangqingyun.gitee.io/qwidgetdemo/iotsystem/](https://feiyangqingyun.gitee.io/qwidgetdemo/iotsystem/)
7. 体验地址:[https://pan.baidu.com/s/1ZxG-oyUKe286LPMPxOrO2A](https://pan.baidu.com/s/1ZxG-oyUKe286LPMPxOrO2A) 提取码:o05q 文件名:bin_iotsystem.zip。
8. 文章导航:[https://qtchina.blog.csdn.net/article/details/121330922](https://qtchina.blog.csdn.net/article/details/121330922)


## 四、效果图





## 五、相关代码
```cpp
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:recision).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 = nodeValue;
            }


            //云端数据同步
            if (AppConfig::UseNetDb) {
                QString sql = QString("update NodeData set NodeValue='%1',SaveTime='%2' where PositionID='%3'").arg(nodeValue).arg(DATETIME).arg(positionID);
                DbData:bNet->append(sql);
            }


            datas[startIndex] = nodeValue;
        }
    }


    emit receiveValue(portName, addr, datas);
}
```

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册