故障诊断算法+软件界面设计+实时监测+故障机理分析+实时故障诊断,实现设备相关测点的实时监测以及运动部件的实时故障诊断,通过多平台语言和技术集成,实现了数据采集、处理、显示和存储的完整闭环。
1.软件原型图
2.通信协议
小端字节序,低位在前,高位在后
校验和是用于验证数据完整性的一种错误校验机制,如果检测到数据损坏,可以选择丢弃该数据包
校验和步骤:从首部开始,按照16位字节进行累加,再将结果按16位取反后得到校验和。
比如第5个字节是0e,第6个字节是04,40E,转换成十进制就是1038数据长度。
第7个字节,包类型0x02数据采集控制应答
第10个字节,包计数,3a(58,第58个UDP包),3b(59),3c,3d,3e,3f,40(64)
第11个字节,为0x01,即为4-20mA的1-8通道数据
若为0x04,即为心跳报文,则发送到心跳报文队列。心跳报文包括采集器温度、湿度、采集器配置(IP,子网掩码,网关,服务器IP地址,时间戳),电流、电压、温度状态开关。
通过定期发送心跳包,可以有效检测和维持UDP连接的活跃状态。在未接收到心跳包时,及时进行重试和超时处理,确保通信系统的可靠性和稳定性。
3.项目问题
- 如何实现Android端与采集器之间的UDP通信?
使用Java编写了一个UDP客户端,通过DatagramSocket监听本地网络的12032端口,采集器和安卓端在同一个网段下,采集器向安卓端发送UDP数据包。
- 在实现UDP通信时遇到了哪些挑战?如何解决的?
实现过程中,我们遇到过数据包丢失和乱序的问题。为了解决这些问题,我们在应用层实现了简单的重传机制,并对数据包进行了编号,以确保数据的完整性和顺序。
- 为什么选择UDP而不是TCP来获取采集器的配置信息和测点数据?
UDP比TCP更轻量级、传输速度更快,对于实时性要求较高的数据采集任务更为适合。而且我们可以容忍少量的数据丢失,并不影响设备的实际监测结果,因此UDP是一个更好的选择。
- 解释下MPAndroidChart库是如何用来实时显示数据的?
MPAndroidChart是一个开源的图表库,提供了丰富的图表类型和自定义选项。我们使用LineChart来展示振动信号的时域和频域变化。首先,我们创建了一个LineDataSet来存储数据点,并定期更新数据集中的值,然后调用notifyDataSetChanged()方法来刷新图表。
- 如何处理大量数据以确保图表的流畅显示?
为了处理大量数据,我们采用了滚动窗口的方式,每次只显示最近的一段时间内的数据。通过限制LineDataSet的最大点数,我们确保了图表的流畅性,并且避免了内存溢出的问题。
- 你是如何实现时频域变化趋势的动态展示的?
动态展示时频域变化趋势,我们使用FFT算法将时域信号转换为频域信号,并将频域数据传递给MPAndroidChart进行展示。每当有新数据到达时,我们会重新计算频域数据并更新图表。
- 请描述一下你是如何在C++中实现故障诊断算法的?
在C++中实现振动信号预处理算法,VMD(k,a)变分模态分解将振动信号分解为k个模态分量,通过加权相关稀疏度指标,筛选IMF分量,最后将全部IMF分量重构,获取降噪后的振动信号。振动提取特征包括:偏度、峭度、峰峰值、均方根、能量、波形因数、振幅因数、裕度因数等。
深度学习故障诊断算法模型使用Python仿真测试,选用Pytorch框架,训练好的模型为model.pth格式转为.pt格式,并存储该文件,C++中加载并调用Libtorch的库模型,torch::load(model.pt)。
模拟训练时的输入数据,vector<torch::IValue> inputs;inputs存放数据,通过module->forward(inputs).toTensor();前向推理获得输出。
- 如何在Android端与C++实现的服务端建立TCP长连接?
在Android端,我们使用Java的Socket类来与C++实现的服务端建立TCP长连接。我们通过Socket的输入输出流进行数据的双向传输,确保Android端可以实时接收故障诊断结果并显示在界面上。
- 为什么使用TCP长连接
当前项目需求只需要稳定的连接和数据传输,TCP长连接可以很好满足这些需求,相比使用更复杂的网络库如Boost、Asio,TCP长连接的实现相对简单,不需要复杂的配置和额外依赖,更容易调试和维护。使用C/C++原生的socket编程接口实现TCP长连接。通过socket()
创建套接字,使用connect()
进行连接,send()
和recv()
进行数据传输。保持连接的存活并处理可能的断线重连。为了防止连接断开或检测连接状态,可以实现心跳包机制,定期发送简单的数据包以确保连接存活。为了提高并发性能,使用多线程处理不同客户端的连接,或者利用select()
/poll()
/epoll()
等多路复用技术处理多个连接。当前项目处于初期阶段,功能和需求还未定型,因此选择更简单、容易实现和维护的方式。后续如果有需要扩展或更高性能的需求,可以再考虑引入更复杂的网络库。
C++中:
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
send(sockfd, buffer, strlen(buffer), 0);
recv(sockfd, buffer, sizeof(buffer), 0);
服务器端:负责接收来自客户端的数据(2048个数据点),处理这些数据并返回诊断结果标签。
#include <iostream>
#include <vector>
#include <netinet/in.h>
#include <unistd.h>
float calculateChecksum(const std::vector<float>& data) {
float checksum = 0;
for (float value : data) {
checksum += value;
}
return checksum;
}
int diagnoseFault(const std::vector<float>& data) {
// 在这里实现故障诊断逻辑,返回诊断结果标签
// 例如返回 0 表示无故障,1 表示某种故障
return 0;
}
void handleClient(int clientSocket) {
while (true) {
// 读取包头
int dataLength;
float receivedChecksum;
ssize_t headerRead = recv(clientSocket, &dataLength, sizeof(dataLength), 0);
if (headerRead <= 0) {
break; // 连接中断
}
recv(clientSocket, &receivedChecksum, sizeof(receivedChecksum), 0);
// 读取包体
int dataSize = dataLength / sizeof(float);
std::vector<float> data(dataSize);
ssize_t bodyRead = recv(clientSocket, data.data(), dataLength, 0);
if (bodyRead <= 0) {
break; // 连接中断
}
// 计算校验和
float calculatedChecksum = calculateChecksum(data);
// 校验校验和
if (calculatedChecksum == receivedChecksum) {
int faultType = diagnoseFault(data);
// 发送故障诊断标签
send(clientSocket, &faultType, sizeof(faultType), 0);
} else {
std::cerr << "Checksum mismatch, data may be corrupted." << std::endl;
int errorType = -1;
send(clientSocket, &errorType, sizeof(errorType), 0);
}
}
close(clientSocket);
}
int main() {
int serverSocket = socket(AF_INET, SOCK_STREAM, 0);
sockaddr_in serverAddr{};
serverAddr.sin_family = AF_INET;
serverAddr.sin_addr.s_addr = INADDR_ANY;
serverAddr.sin_port = htons(12345);
bind(serverSocket, (sockaddr*)&serverAddr, sizeof(serverAddr));
listen(serverSocket, 5);
while (true) {
sockaddr_in clientAddr{};
socklen_t clientAddrSize = sizeof(clientAddr);
int clientSocket = accept(serverSocket, (sockaddr*)&clientAddr, &clientAddrSize);
if (clientSocket >= 0) {
handleClient(clientSocket);
}
}
close(serverSocket);
return 0;
}
Android端:负责发送数据点到服务器,并接收服务器返回的结果。
Java中:DataOutputStream、DataInputStream,dos.writeFloat(),dos.writeInt();
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.net.Socket;
public class FaultDiagnosisClient {
private static final String SERVER_IP = "your.server.ip.address";
private static final int SERVER_PORT = 12345;
private Socket socket;
private DataOutputStream dos;
private DataInputStream dis;
public FaultDiagnosisClient() {
try {
socket = new Socket(SERVER_IP, SERVER_PORT);
dos = new DataOutputStream(socket.getOutputStream());
dis = new DataInputStream(socket.getInputStream());
} catch (Exception e) {
e.printStackTrace();
}
}
private float calculateChecksum(float[] data) {
float checksum = 0;
for (float value : data) {
checksum += value;
}
return checksum;
}
public int sendAndReceiveDiagnosis(float[] data) {
try {
// 计算校验和
float checksum = calculateChecksum(data);
// 发送包头:数据长度和校验和
dos.writeInt(data.length * 4); // 数据长度(2048 * 4 bytes)
dos.writeFloat(checksum); // 校验和
// 发送包体:2048个数据点
for (float value : data) {
dos.writeFloat(value);
}
// 读取返回的故障诊断标签
int faultType = dis.readInt();
return faultType;
} catch (Exception e) {
e.printStackTrace();
}
return -1; // 表示错误
}
public void close() {
try {
if (socket != null) socket.close();
if (dos != null) dos.close();
if (dis != null) dis.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
- 如何确保TCP长连接的稳定性和实时性?
为了确保TCP长连接的稳定性,实现了心跳机制来检测连接状态,并在连接断开时自动重连。同时,对数据传输进行了优化,确保实时性要求得到满足。
- 你是如何使用JNI在Android中调用C++代码的?
我们使用JNI(Java Native Interface)来在Android中调用C++代码。首先,我们编写了一个C++库并生成对应的.so文件,然后在Java代码中通过System.loadLibrary()方法加载该库,并通过native关键字声明本地方法。通过这些步骤,我们可以在Java代码中调用C++实现的信号预处理算法。
- 你能详细描述下信号预处理算法的降噪和特征提取过程吗?
信号预处理算法包括降噪和特征提取两个步骤。降噪使用了小波去噪方法,通过分解信号并去除高频噪声来实现。特征提取则使用了时域和频域特征,如均值、标准差、频谱峰值等。这些特征用于后续的故障诊断。
- 在JNI调用过程中遇到过哪些问题?是如何解决的?
在JNI调用过程中,我们遇到了一些兼容性问题和内存泄漏问题。我们通过仔细检查JNI接口和使用Valgrind工具来检测和修复内存泄漏。同时,我们使用NDK编译器选项来确保兼容性问题得到解决。
- 描述下你在项目中是如何使用SQLite存储和管理数据的?
在项目中,我们使用SQLite来存储设备状态信息和测点配置信息。我们通过SQLiteOpenHelper类创建和管理数据库,并使用SQLiteDatabase类执行SQL操作来插入、更新、删除和查询数据。
- 如何设计数据库表结果以高效存储设备状态信息和测点配置信息?
数据库表结构设计时,我们创建了多个表来分别存储设备信息、测点配置和测点数据。每个表都有相应的主键和外键,以确保数据的完整性和一致性。例如,设备表包含设备ID、设备名称等字段,测点表包含测点ID、设备ID、测点位置等字段。
- 为什么选择文件流来定期存储测点数据?如何实现的?
我们选择文件流来定期存储测点数据是为了确保数据的持久性和安全性。通过定期将数据写入文件,我们可以在数据库出现问题时进行数据恢复。实现方面,我们使用了Java的FileOutputStream类和BufferedWriter类来将数据写入文件,并通过定时任务定期执行这一操作。