Qt Modbus 2 通信实现

上一文章主要了解下Modbus协议和事务处理流程,本章则直接贴放代码实现qt 上位机与温控器通讯。项目主要实现定时读取温度、设置温度、开始加热和停止加热四个功能。
采用的是 Modbus Rtu 通信

1 Qt modbus 模块依赖

QT += serialbus serialport

源代码

.h文件

#ifndef MODBUSDEVICE_H
#define MODBUSDEVICE_H#include <QObject>
#include <QModbusDevice>
#include <QSerialPort>
#include <QModbusPdu>
#include <QModbusRtuSerialMaster>
#include <QSerialPortInfo>
#include <QTimer>
#define ADDR_READ_PV  "0100"
#define ADDR_READ_SV  "0300"
#define ADDR_SET_SV   "0300"
#define ADDR_RUN  "0190"
class ModbusDevice : public QObject
{Q_OBJECT
public:explicit ModbusDevice(QObject *parent = nullptr);~ModbusDevice();void setAddresss(const QVector<quint8> addrs);Q_INVOKABLE void setCom(const QString &com);Q_INVOKABLE QString getCom();void open(const QString &portname);void closeAndDelete();Q_INVOKABLE void close();Q_INVOKABLE void connect(const QString &com);Q_INVOKABLE void disconnect();Q_INVOKABLE bool state();void sendReadPV();void sendSetSV(const float v);void sendRun();void setStopSync(const quint8 addr);void sendStop();void sendSetSV(const quint8 addr,const float v);void sendReadPV(const quint8 addr);void sendReadSV(const quint8 addr);void sendRun(const quint8 addr);void sendStop(const quint8 addr);void stopTimer();signals:void sigError(const QString &msg);void sigDisconnected();void sigConnected();void sigModbusSendError(const QString &msg);void sigInfo(const QString &msg);void sigGetPV(const int index,const float v);void sigGetSV(const int index,const float v);
private slots:void onReplyTimeout();
private:void creatDevice();void reconnect();void creatTimer();void startTimer();void handleSerialPortDisconnected();void readValue(const quint8 address,const QString addr);void setRun(const quint8 address,const bool isRun);void startReplyTimer();void stopReplyTimer();QSerialPort::BaudRate   baudRate=QSerialPort::Baud9600;QSerialPort::DataBits   dataBits=QSerialPort::Data8;QSerialPort::Parity     parity=QSerialPort::EvenParity;QSerialPort::StopBits   stopBits=QSerialPort::TwoStop;QTimer m_pvTimer;QTimer m_replyTimer;QString m_strCom;QModbusClient *m_device = nullptr;QVector<quint8> m_deviceAddrs;int m_errCount = 0;int m_timeoutContinousCount = 0;
};#endif // MODBUSDEVICE_H

cpp文件

#include "modbusdevice.h"
#include <QDebug>
#include <QModbusReply>
#include <QCoreApplication>
#include <QThread>
ModbusDevice::ModbusDevice(QObject *parent) : QObject(parent)
{m_deviceAddrs << 1 <<2;creatTimer();creatDevice();
}ModbusDevice::~ModbusDevice()
{stopTimer();closeAndDelete();
}void ModbusDevice::setAddresss(const QVector<quint8> addrs)
{m_deviceAddrs = addrs;
}void ModbusDevice::open(const QString &portname)
{if (!m_device){sigError(QString("串口连接失败,请排查线路"));return;}m_device->setConnectionParameter(QModbusDevice::SerialPortNameParameter,portname);m_device->connectDevice();
}void ModbusDevice::closeAndDelete()
{if (!m_device)return;m_device->disconnectDevice();m_device->disconnect();delete m_device;m_device = nullptr;
}void ModbusDevice::close()
{if (!m_device)return;stopTimer();m_device->disconnectDevice();
}void ModbusDevice::connect(const QString &com)
{setCom(com);open(com);
}void ModbusDevice::disconnect()
{if (!m_device)return ;close();
}bool ModbusDevice::state()
{if (!m_device)return false;return m_device->state() == QModbusDevice::ConnectedState;
}void ModbusDevice::sendReadPV()
{for(auto v:m_deviceAddrs){sendReadPV(v);}
}void ModbusDevice::sendSetSV(const float v)
{for(auto addr:m_deviceAddrs){sendSetSV(addr,v);}
}void ModbusDevice::sendRun()
{for(auto addr:m_deviceAddrs){sendRun(addr);}
}void ModbusDevice::setStopSync(const quint8 addr)
{if (!m_device)return ;if(m_device->state() != QModbusDevice::ConnectedState)return;QModbusReply *reply = nullptr;const QByteArray pduData = QByteArray::fromHex("01900000");quint16 fc = 0x06;reply = m_device->sendRawRequest(QModbusRequest(QModbusRequest::FunctionCode(fc), pduData),addr);while (!reply->isFinished()) {QCoreApplication::processEvents();if (QCoreApplication::instance()->closingDown()) {break;}QThread::msleep(10);}if (reply->error() == QModbusDevice::NoError) {qDebug() << "Command sent successfully.";} else {qWarning() << "Failed to send command:" << reply->errorString();}reply->deleteLater();
}void ModbusDevice::sendStop()
{for(auto addr:m_deviceAddrs){sendStop(addr);}
}void ModbusDevice::sendSetSV(const quint8 addr,const float v)
{if (!m_device)return ;if(m_device->state() != QModbusDevice::ConnectedState)return;QModbusReply *reply = nullptr;short tmp = (v*10);QString msg =  QString("0300%1").arg(tmp, 4, 16, QLatin1Char('0')).toUpper();const QByteArray pduData = QByteArray::fromHex(msg.toLatin1());quint16 fc = 0x06;reply = m_device->sendRawRequest(QModbusRequest(QModbusRequest::FunctionCode(fc), pduData),addr);startReplyTimer();if (!reply->isFinished()) {QObject::connect(reply, &QModbusReply::finished, this, [=]() {stopReplyTimer();if (reply->error() == QModbusDevice::NoError) {sigInfo(QString("设置指令发送成功"));qDebug() << "setD1Value reveive:" << reply->rawResult();} else if (reply->error() == QModbusDevice::ProtocolError) {QString msg = QString("setValue response error: %1 Mobus exception: 0x%2").arg(reply->errorString()).arg(reply->rawResult().exceptionCode(), -1, 16);qDebug() << msg;emit sigModbusSendError(msg);} else {QString msg = (tr("setValue response error: %1 (code: 0x%2)").arg(reply->errorString()).arg(reply->error(), -1, 16));qDebug() << msg;emit sigModbusSendError(msg);}reply->deleteLater();});} else {stopReplyTimer();// broadcast replies return immediatelyqDebug() << "Receive: Synchronous response pdu: " << reply->rawResult() << Qt::endl;reply->deleteLater();}
}void ModbusDevice::sendReadPV(const quint8 addr)
{readValue(addr,ADDR_READ_PV);
}void ModbusDevice::sendReadSV(const quint8 addr)
{readValue(addr,ADDR_READ_SV);
}void ModbusDevice::sendRun(const quint8 addr)
{setRun(addr,true);
}void ModbusDevice::sendStop(const quint8 addr)
{setRun(addr,false);
}void ModbusDevice::setCom(const QString &com)
{if(com != m_strCom){m_strCom = com;}
}QString ModbusDevice::getCom()
{return m_strCom;
}void ModbusDevice::reconnect()
{if (!m_device)return ;if (m_device->state() != QModbusDevice::ConnectedState) {qInfo() << "Attempting to reconnect...";m_device->connectDevice();}
}void ModbusDevice::creatDevice()
{m_device = new QModbusRtuSerialMaster;//m_device->setConnectionParameter(QModbusDevice::SerialPortNameParameter,portname);m_device->setConnectionParameter(QModbusDevice::SerialParityParameter, parity);m_device->setConnectionParameter(QModbusDevice::SerialDataBitsParameter,dataBits);m_device->setConnectionParameter(QModbusDevice::SerialStopBitsParameter,stopBits);m_device->setConnectionParameter(QModbusDevice::SerialBaudRateParameter,baudRate);m_device->setTimeout(1000);m_device->setNumberOfRetries(1);QObject::connect(m_device, &QModbusDevice::errorOccurred, this, [=](QModbusDevice::Error) {QString msg = m_device->errorString();qDebug().noquote() << QStringLiteral("Error: %1").arg(msg);emit sigError(msg);}, Qt::QueuedConnection);QObject::connect(m_device, &QModbusDevice::stateChanged, [=](QModbusDevice::State state) {switch (state) {case QModbusDevice::UnconnectedState:{qDebug().noquote() << QStringLiteral("State: Entered unconnected state.");stopTimer();emit sigDisconnected();}break;case QModbusDevice::ConnectingState:qDebug().noquote() << QStringLiteral("State: Entered connecting state.");break;case QModbusDevice::ConnectedState:{qDebug().noquote() << QStringLiteral("State: Entered connected state.");sendReadPV();startTimer();emit sigConnected();}break;case QModbusDevice::ClosingState:qDebug().noquote() << QStringLiteral("State: Entered closing state.");break;}});
}void ModbusDevice::handleSerialPortDisconnected(){qWarning() << "Serial port disconnected.";close();}void ModbusDevice::creatTimer()
{m_pvTimer.setInterval(4000);QObject::connect(&m_pvTimer,&QTimer::timeout,this,[=]{sendReadPV();});m_replyTimer.setInterval(3000);m_replyTimer.setSingleShot(true);QObject::connect(&m_replyTimer,SIGNAL(timeout()),this,SLOT(onReplyTimeout()));
}void ModbusDevice::startTimer()
{m_pvTimer.start();
}void ModbusDevice::stopTimer()
{m_pvTimer.stop();
}void ModbusDevice::onReplyTimeout()
{m_timeoutContinousCount++;qDebug() << __func__;sigModbusSendError(QString("发包响应超时,请排查线路"));if(m_timeoutContinousCount>3){sigError(QString("通信超时,请排查线路"));m_timeoutContinousCount=0;close();}
}void ModbusDevice::readValue(const quint8 address,const QString addr)
{if (!m_device)return ;if(m_device->state() != QModbusDevice::ConnectedState)return;QModbusReply *reply = nullptr;QString msg = addr+"0001";const QByteArray pduData = QByteArray::fromHex(msg.toLatin1());//qDebug() << "Send: Sending PDU with predefined function code.";quint16 fc = 0x03;reply = m_device->sendRawRequest(QModbusRequest(QModbusRequest::FunctionCode(fc), pduData),address);startReplyTimer();if (!reply->isFinished()) {QObject::connect(reply, &QModbusReply::finished, this, [=](){stopReplyTimer();if (reply->error() == QModbusDevice::NoError) {const QByteArray rawData = reply->rawResult().data();//ex:0x030200dcQByteArray lastTwoBytes = rawData.mid(rawData.size() - 2, 2);// 使用 QDataStream 进行字节序转换QDataStream stream(lastTwoBytes);quint16 shortValue;stream >> shortValue;if(addr == ADDR_READ_PV){emit sigGetPV(address,(float)shortValue/10);}else if(addr == ADDR_READ_SV){emit sigGetSV(address,(float)shortValue/10);}} else if (reply->error() == QModbusDevice::ProtocolError) {QString msg =  QString("readValue response error: %1 Mobus exception: 0x%2").arg(reply->errorString()).arg(reply->rawResult().exceptionCode(), -1, 16);qDebug() << msg;emit sigModbusSendError(msg);} else {QString msg =  (tr("readValue response error: %1 (code: 0x%2)").arg(reply->errorString()).arg(reply->error(), -1, 16));qDebug() << msg;m_errCount ++;if(m_errCount > 3){emit sigError(QString("温控%1 连接已断开").arg(address));m_errCount = 0;reconnect();}emit sigModbusSendError(msg);}reply->deleteLater();});} else {stopReplyTimer();// broadcast replies return immediatelyqDebug() << "Receive: Synchronous response pdu: " << reply->rawResult() << Qt::endl;reply->deleteLater();}
}void ModbusDevice::setRun(const quint8 address,const bool isRun)
{if (!m_device)return ;if(m_device->state() != QModbusDevice::ConnectedState)return;QModbusReply *reply = nullptr;const QByteArray pduData = isRun? QByteArray::fromHex("01900001"):QByteArray::fromHex("01900000");quint16 fc = 0x06;reply = m_device->sendRawRequest(QModbusRequest(QModbusRequest::FunctionCode(fc), pduData),address);startReplyTimer();if (!reply->isFinished()) {QObject::connect(reply, &QModbusReply::finished, this, [=]() {stopReplyTimer();if (reply->error() == QModbusDevice::NoError) {if(isRun)sigInfo(QString("开始指令发送成功"));else {sigInfo(QString("停止指令发送成功"));}qDebug() << "setRun reveive:" << reply->rawResult();} else if (reply->error() == QModbusDevice::ProtocolError) {QString msg =  QString("setRun response error: %1 Mobus exception: 0x%2").arg(reply->errorString()).arg(reply->rawResult().exceptionCode(), -1, 16);qDebug() << msg;emit sigModbusSendError(msg);} else {QString msg = (tr("setRun response error: %1 (code: 0x%2)").arg(reply->errorString()).arg(reply->error(), -1, 16));qDebug() << msg;emit sigModbusSendError(msg);}reply->deleteLater();});} else {// broadcast replies return immediatelystopReplyTimer();qDebug() << "setRun Receive: Synchronous response pdu: " << reply->rawResult() << Qt::endl;reply->deleteLater();}
}void ModbusDevice::startReplyTimer()
{m_replyTimer.start();
}void ModbusDevice::stopReplyTimer()
{m_replyTimer.stop();
}

异常处理

1. 通信断开异常检测

测试过程中发现,拔掉串口线,QModbusClient 并未检测到任何异常,这时候发包也不会有任何异常提示,所以在发包之后自己启动一个timer,来通过超时判断是否串口连接出现问题。有更好的方法可以分享下。

void ModbusDevice::sendSetSV(const quint8 addr,const float v)
{if (!m_device)return ;if(m_device->state() != QModbusDevice::ConnectedState)return;QModbusReply *reply = nullptr;short tmp = (v*10);QString msg =  QString("0300%1").arg(tmp, 4, 16, QLatin1Char('0')).toUpper();const QByteArray pduData = QByteArray::fromHex(msg.toLatin1());quint16 fc = 0x06;reply = m_device->sendRawRequest(QModbusRequest(QModbusRequest::FunctionCode(fc), pduData),addr);startReplyTimer();if (!reply->isFinished()) {QObject::connect(reply, &QModbusReply::finished, this, [=]() {stopReplyTimer();if (reply->error() == QModbusDevice::NoError) {sigInfo(QString("设置指令发送成功"));qDebug() << "setD1Value reveive:" << reply->rawResult();} else if (reply->error() == QModbusDevice::ProtocolError) {QString msg = QString("setValue response error: %1 Mobus exception: 0x%2").arg(reply->errorString()).arg(reply->rawResult().exceptionCode(), -1, 16);qDebug() << msg;emit sigModbusSendError(msg);} else {QString msg = (tr("setValue response error: %1 (code: 0x%2)").arg(reply->errorString()).arg(reply->error(), -1, 16));qDebug() << msg;emit sigModbusSendError(msg);}reply->deleteLater();});} else {stopReplyTimer();// broadcast replies return immediatelyqDebug() << "Receive: Synchronous response pdu: " << reply->rawResult() << Qt::endl;reply->deleteLater();}
}

其中 startReplyTimer() 和 stopReplyTimer() 来实现链路检测。

2. 程序关闭时,需要发送一条指令,等发送和响应结束后才能关闭App

因为是加热设备,为了安全起见,程序关闭时,会主动停止加热。此时就需要实现一个同步发送的指令。等待返回后才能关闭App

void ModbusDevice::setStopSync(const quint8 addr)
{if (!m_device)return ;if(m_device->state() != QModbusDevice::ConnectedState)return;QModbusReply *reply = nullptr;const QByteArray pduData = QByteArray::fromHex("01900000");quint16 fc = 0x06;reply = m_device->sendRawRequest(QModbusRequest(QModbusRequest::FunctionCode(fc), pduData),addr);while (!reply->isFinished()) {QCoreApplication::processEvents();if (QCoreApplication::instance()->closingDown()) {break;}QThread::msleep(10);}if (reply->error() == QModbusDevice::NoError) {qDebug() << "Command sent successfully.";} else {qWarning() << "Failed to send command:" << reply->errorString();}reply->deleteLater();
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/460884.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

飞桨首创 FlashMask :加速大模型灵活注意力掩码计算,长序列训练的利器

在 Transformer 类大模型训练任务中&#xff0c;注意力掩码&#xff08;Attention Mask&#xff09;一方面带来了大量的冗余计算&#xff0c;另一方面因其 O ( N 2 ) O(N^2) O(N2)巨大的存储占用导致难以实现长序列场景的高效训练&#xff08;其中 N N N为序列长度&#xff09;…

乘云而上,OceanBase再越山峰

一座山峰都是一个挑战&#xff0c;每一次攀登都是一次超越。 商业数据库时代&#xff0c;面对国外数据库巨头这座大山&#xff0c;实现市场突破一直都是中国数据库产业多年夙愿&#xff0c;而OceanBase在金融核心系统等领域的攻坚克难&#xff0c;为产业突破交出一副令人信服的…

为什么要使用Golang以及如何入门

什么是golang&#xff1f; Go是一种开放源代码的编程语言&#xff0c;于2009年首次发布&#xff0c;由Google的Rob Pike&#xff0c;Robert Griesemer和Ken Thompson开发。基于C的语法&#xff0c;它进行了一些更改和改进&#xff0c;以安全地管理内存使用&#xff0c;管理对象…

《文心一言插件设计与开发》赛题三等奖方案 | NoteTable

一年一度的 CCF大数据与计算智能大赛&#xff08;简称2024 CCF BDCI大赛&#xff09;又开始啦~~ 程序员们可冲一波嗷~ 大赛地址&#xff1a;http://go.datafountain.cn/6506 现在我们再次释放往届获奖方案&#xff0c; 为新一届大赛的同学们提供一些方案和灵感参考~ 大家借鉴借…

el-dialog支持全局拖拽功能

1.首先在全局的组件实现拖拽功能&#xff0c;结构如下 dialogDrag.vue的内容 <script>export default {mounted() {// 获取当前的dialog及其headerlet aimDialog this.$el.getElementsByClassName(el-dialog)[0];let aimHeader this.$el.getElementsByClassName(el-d…

XCode16中c++头文件找不到解决办法

XCode16中新建Framework&#xff0c;写完自己的c代码后&#xff0c;提示“<string> file not found”等诸如此类找不到c头文件的错误。 工程结构如下&#xff1a; App是测试应用&#xff0c;BoostMath是Framework。基本结构可以参考官方demo&#xff1a;Mix Swift and …

开源代码管理平台Gitlab如何本地化部署并实现公网环境远程访问私有仓库

文章目录 前言1. 下载Gitlab2. 安装Gitlab3. 启动Gitlab4. 安装cpolar5. 创建隧道配置访问地址6. 固定GitLab访问地址6.1 保留二级子域名6.2 配置二级子域名 7. 测试访问二级子域名 前言 本文主要介绍如何在Linux CentOS8 中搭建GitLab私有仓库并且结合内网穿透工具实现在公网…

JavaEE初阶---网络原理(四)--IP协议/DNS协议

文章目录 1.初识网络层&#xff08;了解即可&#xff09;2.地址管理2.1动态分配2.2网络地址转换2.3IP-v6最终解 3.网段划分4.以太网协议--数据链路层5.DNS应用层协议 1.初识网络层&#xff08;了解即可&#xff09; 网络层做的事情就是下面的两个&#xff1a; 1&#xff09;地…

4.2-6 使用Hadoop WebUI

文章目录 1. 查看HDFS集群状态1.1 端口号说明1.2 用主机名访问1.3 主节点状态1.4 用IP地址访问1.5 查看数据节点 2. 操作HDFS文件系统2.1 查看HDFS文件系统2.2 在HDFS上创建目录2.3 上传文件到HDFS2.4 删除HDFS文件和目录 3. 查看YARN集群状态4. 实战总结 1. 查看HDFS集群状态 …

EMS专题 | 5个必须知道的温度监测系统入门知识

在保护温度敏感资产方面&#xff0c;可靠的温度监测技术扮演着至关重要的角色。为了帮助您深入了解这一关键技术&#xff0c;我们特别推出了EMS&#xff08;环境监测系统&#xff09;专题文章系列。内容将由浅入深&#xff0c;从基础原理到实际应用&#xff0c;从行业标准到解决…

代码随想录-字符串-反转字符串中的单词

题目 题解 法一:纯粹为了做出本题&#xff0c;暴力解 没有技巧全是感情 class Solution {public String reverseWords(String s) {//首先去除首尾空格s s.trim();String[] strs s.split("\\s");StringBuilder sb new StringBuilder();//定义一个公共的字符反转…

关于Android Studio Koala Feature Drop | 2024.1.2下载不了插件的解决办法

解决 androidStudio Settings->Plugins下载插件&#xff0c;点击install后没反应&#xff0c;同时插件描述相关显示不出来 第一步&#xff1a; 第二步&#xff1a; 点击设置&#xff0c;勾选Auto-detect proxy settings&#xff0c;输入网址 https://plugins.jetbrains.com…

[论文阅读] Improved Baselines with Visual Instruction Tuning

启发&#xff1a; 1、LLaVA-1.5和LLaVA以及其他大模型相比&#xff0c;做出了哪些改进&#xff1f; &#xff08;1&#xff09;使用CLIP-ViT-L-336px作为视觉编码器&#xff0c;使模型能处理336px的高分辨率图像&#xff0c;这使得模型能从图像中提取出更多细节信息。此外&am…

大语言模型(LLM)快速理解

自2022年&#xff0c;ChatGPT发布之后&#xff0c;大语言模型&#xff08;Large Language Model&#xff09;&#xff0c;简称LLM掀起了一波狂潮。作为学习理解LLM的开始&#xff0c;先来整体理解一下大语言模型。 一、发展历史 大语言模型的发展历史可以追溯到早期的语言模型…

「C/C++」C++标准库之#include<fstream>文件流

✨博客主页何曾参静谧的博客&#x1f4cc;文章专栏「C/C」C/C程序设计&#x1f4da;全部专栏「VS」Visual Studio「C/C」C/C程序设计「UG/NX」BlockUI集合「Win」Windows程序设计「DSA」数据结构与算法「UG/NX」NX二次开发「QT」QT5程序设计「File」数据文件格式「PK」Parasoli…

十六:Python学习笔记-- 爬虫(2)requests 模块详解

目录 安装 requests 模块 基本请求方法 GET 请求 POST 请求 PUT 请求 DELETE 请求 添加请求头&#xff1a; 处理查询参数&#xff1a; 文件上传&#xff1a; 常见响应状态码 访问超时 cookie的查询和设置 查询 Cookies 设置 Cookies 设置爬虫代理 小试牛刀 安装 …

无人机敏捷反制技术算法详解!

一、技术概述 无人机敏捷反制技术算法主要通过对非法入侵的无人机进行快速、精准的探测、识别、干扰和摧毁等操作&#xff0c;从而消除无人机威胁&#xff0c;保障人员和财产安全。这一技术涵盖了多种技术手段&#xff0c;如无线电干扰、激光打击、网捕等&#xff0c;并需要综…

8个最佳iMacros替代方案(2024)

1、前言 iMacros是web自动化、抓取和测试的领先工具&#xff0c;它提供了一个浏览器扩展和桌面工具&#xff0c;可以轻松地自动化你的日常任务&#xff0c;然而&#xff0c;自动化所需的关键功能仅在高级版本中可用&#xff0c;iMacros几乎没有其他缺点。 如图所示&#xff1…

数据库数据恢复—Oracle ASM磁盘组掉线 ,ASM实例无法挂载的数据恢复案例

Oracle数据库数据恢复环境&故障&#xff1a; Oracle ASM磁盘组由4块磁盘组成。Oracle ASM磁盘组掉线 &#xff0c;ASM实例不能mount。 Oracle数据库故障分析&恢复方案&#xff1a; 数据库数据恢复工程师对组成ASM磁盘组的磁盘进行分析。对ASM元数据进行分析发现ASM存储…

解决电脑突然没有声音

问题描述&#xff1a;电脑突然没有声音了&#xff0c;最近没有怎么动过系统&#xff0c;没有安装或者卸载过什么软件&#xff0c;也没有安装或者卸载过驱动程序&#xff0c;怎么就没有声音了呢&#xff1f; 问题分析&#xff1a;仔细观察&#xff0c;虽然音量按钮那边看不到什…