-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathModbusServer.cpp
More file actions
1177 lines (987 loc) · 35.2 KB
/
Copy pathModbusServer.cpp
File metadata and controls
1177 lines (987 loc) · 35.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/**
* @file ModbusServer.cpp
* @brief Modbus TCP/RTU服务器实现
*
* 支持Modbus TCP和RTU两种模式,实现标准Modbus功能码以及文件记录操作
*/
#include "ModbusServer.h"
#include <QtEndian>
#include <QDebug>
#include <QFile>
#include <QUrl>
ModbusServer::ModbusServer(QObject *parent)
: QObject(parent)
, m_tcpServer(nullptr)
, m_serialPort(nullptr)
, m_rtuTimer(nullptr)
, m_running(false)
, m_mode(ModeTCP)
, m_requestCount(0)
, m_lastFunctionCode(0)
, m_slaveId(1)
{
// 创建数据存储
m_dataStore = new ModbusDataStore(this); // 加上this可用进行自动管理子对象生命周期
m_functionHandler = new ModbusFunctionHandler(m_dataStore, this);
m_fileStore = new FileStore(this);
m_addressStore = new FileAddressStore(this);
// 连接信号
connect(m_functionHandler, &ModbusFunctionHandler::requestProcessed,
this, [this](quint8 fc, bool success)
{
Q_UNUSED(success);
incrementRequestCount();
emit requestReceived(fc); });
// 连接内存警告信号
connect(m_dataStore, &ModbusDataStore::memoryWarning,
this, [this](size_t totalItems)
{
QString report = getMemoryUsageReport();
qWarning() << "[ModbusServer] 内存使用警告:" << totalItems << "项";
emit memoryWarning(totalItems, report); });
}
ModbusServer::~ModbusServer()
{
stop();
}
// ========== TCP 服务器 ==========
bool ModbusServer::startTcp(quint16 port)
{
if (m_running)
{
stop();
}
m_tcpServer = new QTcpServer(this);
connect(m_tcpServer, &QTcpServer::newConnection, this, &ModbusServer::onNewTcpConnection); // 参数依次为:发布对象,信号,接受对象,槽函数
if (!m_tcpServer->listen(QHostAddress::Any, port))
{
setStatusMessage(QString("TCP 启动失败: %1").arg(m_tcpServer->errorString()));
emit errorOccurred(m_statusMessage);
delete m_tcpServer;
m_tcpServer = nullptr;
return false;
}
m_running = true;
m_mode = ModeTCP;
m_requestCount = 0;
setStatusMessage(QString("TCP 服务器运行中 (端口 %1)").arg(port));
emit runningChanged(true);
emit modeChanged(m_mode);
emit requestCountChanged(m_requestCount);
return true;
}
void ModbusServer::stopTcp()
{
if (m_tcpServer)
{
m_tcpServer->close();
for (QTcpSocket *socket : m_tcpClients)
{
socket->disconnectFromHost();
socket->deleteLater();
}
m_tcpClients.clear();
m_tcpBuffers.clear();
delete m_tcpServer;
m_tcpServer = nullptr;
}
}
void ModbusServer::onNewTcpConnection()
{
while (m_tcpServer->hasPendingConnections())
{
QTcpSocket *socket = m_tcpServer->nextPendingConnection();
m_tcpClients.append(socket);
m_tcpBuffers[socket] = QByteArray();
connect(socket, &QTcpSocket::readyRead, this, &ModbusServer::onTcpReadyRead);
connect(socket, &QTcpSocket::disconnected, this, &ModbusServer::onTcpDisconnected);
}
}
void ModbusServer::onTcpReadyRead()
{
QTcpSocket *socket = qobject_cast<QTcpSocket *>(sender());
if (!socket)
return;
QByteArray &buffer = m_tcpBuffers[socket];
buffer.append(socket->readAll());
// 处理所有完整的请求
while (buffer.size() >= 8)
{ // MBAP 头 (7 字节) + 至少 1 字节 PDU
// 读取长度字段
quint16 length = qFromBigEndian<quint16>(reinterpret_cast<const uchar *>(buffer.data() + 4));
int totalLength = 6 + length; // MBAP 头前 6 字节 + 长度
if (buffer.size() < totalLength)
{
break; // 等待更多数据
}
// 提取完整的 ADU
QByteArray adu = buffer.left(totalLength);
buffer.remove(0, totalLength);
// 处理请求
QByteArray response = processTcpRequest(adu);
if (!response.isEmpty())
{
socket->write(response);
}
}
}
void ModbusServer::onTcpDisconnected()
{
QTcpSocket *socket = qobject_cast<QTcpSocket *>(sender());
if (!socket)
return;
qDebug() << "客户端断开连接:" << socket->peerAddress().toString();
m_tcpClients.removeOne(socket);
m_tcpBuffers.remove(socket);
socket->deleteLater();
}
QByteArray ModbusServer::processTcpRequest(const QByteArray &adu)
{
if (adu.size() < 8)
{
return QByteArray();
}
// 提取 MBAP 头
// 将constchar*转换为const uchar*以符合qFromBigEndian的参数要求
quint16 transactionId = qFromBigEndian<quint16>(reinterpret_cast<const uchar *>(adu.data()));
quint16 protocolId = qFromBigEndian<quint16>(reinterpret_cast<const uchar *>(adu.data() + 2));
quint16 length = qFromBigEndian<quint16>(reinterpret_cast<const uchar *>(adu.data() + 4));
quint8 unitId = static_cast<quint8>(adu[6]);
// 验证协议 ID
if (protocolId != 0)
{
return QByteArray();
}
if (m_slaveId != 0 && unitId != static_cast<quint8>(m_slaveId))
{
return QByteArray();
}
// 提取 PDU,截取前7字节后的数据
/* 例如00 01 00 00 00 06 02 03 00 12 00 04的前7个字节:
| 字节 | 含义 | 值(十六进制) | 说明 |
|------|------|----------------|------|
| 0–1 | Transaction ID | `0x0001` | 请求 ID = 1 |
| 2–3 | Protocol ID | `0x0000` | Modbus/TCP 标准 |
| 4–5 | Length | `0x0006` | 后续有 6 字节(Unit ID + PDU) |
| 6 | Unit ID | `0x02` | 目标从站地址 = 2 |
| 7 | Function Code | `0x03` | 读保持寄存器(Read Holding Registers) |
| 8–9 | Starting Address | `0x0012` | 起始寄存器地址 = 0x12 = 18(十进制) |
| 10–11| Quantity | `0x0004` | 读取数量 = 4 个寄存器(8 字节数据) |
*/
QByteArray pdu = adu.mid(7);
quint8 functionCode = static_cast<quint8>(pdu[0]);
// 发送接收报文信号到UI
QString recvPacket = QString("TCP 请求 - FC %1 (0x%2) PDU:%3字节")
.arg(functionCode)
.arg(functionCode, 2, 16, QChar('0'))
.toUpper()
.arg(pdu.size());
emit packetReceived(formatPacket(adu, "← 接收"));
qDebug() << recvPacket;
// 更新最后接收到的功能码
if (m_lastFunctionCode != functionCode)
{
m_lastFunctionCode = functionCode;
emit lastFunctionCodeChanged(functionCode);
}
// 路由到对应处理器
QByteArray responsePdu = routeFunctionCode(functionCode, pdu);
if (responsePdu.isEmpty())
{
return QByteArray();
}
// 构建响应 ADU
QByteArray responseAdu;
quint16 txId = qToBigEndian(transactionId);
responseAdu.append(reinterpret_cast<const char *>(&txId), 2);
quint16 prId = qToBigEndian(quint16(0));
responseAdu.append(reinterpret_cast<const char *>(&prId), 2);
quint16 len = qToBigEndian(quint16(responsePdu.size() + 1));
responseAdu.append(reinterpret_cast<const char *>(&len), 2);
responseAdu.append(static_cast<char>(unitId));
responseAdu.append(responsePdu);
emit packetSent(formatPacket(responseAdu, "→ 发送"));
return responseAdu;
}
// ========== RTU 服务器 ==========
bool ModbusServer::startRtu(const QString &portName, int baudRate)
{
if (m_running)
{
stop();
}
m_serialPort = new QSerialPort(this);
m_serialPort->setPortName(portName);
m_serialPort->setBaudRate(baudRate);
m_serialPort->setDataBits(QSerialPort::Data8);
m_serialPort->setParity(QSerialPort::NoParity);
m_serialPort->setStopBits(QSerialPort::OneStop);
m_serialPort->setFlowControl(QSerialPort::NoFlowControl);
if (!m_serialPort->open(QIODevice::ReadWrite))
{
setStatusMessage(QString("RTU 启动失败: %1").arg(m_serialPort->errorString()));
emit errorOccurred(m_statusMessage);
delete m_serialPort;
m_serialPort = nullptr;
return false;
}
connect(m_serialPort, &QSerialPort::readyRead, this, &ModbusServer::onRtuReadyRead);
connect(m_serialPort, &QSerialPort::errorOccurred, this, &ModbusServer::onRtuError);
// 创建帧间隔定时器
// 对于9600波特率,一个完整帧(最多256字节)传输需要约300ms
// 使用更保守的超时时间,确保完整帧都能接收完毕
m_rtuTimer = new QTimer(this);
int charTime = (11 * 1000) / baudRate; // 毫秒
int timeout = qMax(50, charTime * 35); // 至少50ms,或35个字符时间
m_rtuTimer->setInterval(timeout);
m_rtuTimer->setSingleShot(true);
qDebug() << "RTU 定时器间隔设置为:" << timeout << "ms (字符时间:" << charTime << "ms, 波特率:" << baudRate << ")";
connect(m_rtuTimer, &QTimer::timeout, this, [this]()
{
if (!m_rtuBuffer.isEmpty()) {
qDebug() << "⏱ 定时器超时,处理缓冲区数据,长度:" << m_rtuBuffer.size();
QByteArray response = processRtuRequest(m_rtuBuffer);
if (!response.isEmpty() && m_serialPort) {
m_serialPort->write(response);
}
m_rtuBuffer.clear();
} });
m_running = true;
m_mode = ModeRTU;
m_requestCount = 0;
setStatusMessage(QString("RTU 服务器运行中 (%1, %2)").arg(portName).arg(baudRate));
emit runningChanged(true);
emit modeChanged(m_mode);
emit requestCountChanged(m_requestCount);
return true;
}
void ModbusServer::stopRtu()
{
if (m_rtuTimer)
{
m_rtuTimer->stop();
delete m_rtuTimer;
m_rtuTimer = nullptr;
}
if (m_serialPort)
{
m_serialPort->close();
delete m_serialPort;
m_serialPort = nullptr;
}
m_rtuBuffer.clear();
}
void ModbusServer::onRtuReadyRead()
{
if (!m_serialPort)
return;
// 读取串口数据并追加到缓冲区
m_rtuBuffer.append(m_serialPort->readAll());
// 检查是否已接收到完整帧(最小4字节:从站地址 + 功能码 + CRC)
if (m_rtuBuffer.size() >= 4)
{
quint8 functionCode = static_cast<quint8>(m_rtuBuffer.at(1));
int expectedLength = getExpectedFrameLength(functionCode, m_rtuBuffer);
if (expectedLength > 0 && m_rtuBuffer.size() >= expectedLength)
{
// 已接收完整帧,立即处理
m_rtuTimer->stop();
QByteArray response = processRtuRequest(m_rtuBuffer);
if (!response.isEmpty() && m_serialPort)
{
m_serialPort->write(response);
}
m_rtuBuffer.clear();
return;
}
}
// 帧不完整,重启定时器等待更多数据
m_rtuTimer->start();
}
void ModbusServer::onRtuError(QSerialPort::SerialPortError error)
{
if (error != QSerialPort::NoError)
{
setStatusMessage(QString("RTU 错误: %1").arg(m_serialPort->errorString()));
emit errorOccurred(m_statusMessage);
}
}
QByteArray ModbusServer::processRtuRequest(const QByteArray &adu)
{
emit packetReceived(formatPacket(adu, "← RTU接收"));
// 验证帧长度(最小4字节:从站地址 + 功能码 + CRC)
if (adu.size() < 4)
{
qWarning() << "RTU请求长度不足:" << adu.size() << "字节";
return QByteArray();
}
// 验证CRC校验码
quint16 receivedCrc = qFromLittleEndian<quint16>(reinterpret_cast<const uchar *>(adu.data() + adu.size() - 2));
quint16 calculatedCrc = calculateCRC(adu.left(adu.size() - 2));
if (receivedCrc != calculatedCrc)
{
qWarning() << "RTU CRC校验失败";
return QByteArray();
}
// 提取从站地址和PDU(协议数据单元)
quint8 slaveAddress = static_cast<quint8>(adu[0]);
QByteArray pdu = adu.mid(1, adu.size() - 3);
quint8 functionCode = static_cast<quint8>(pdu[0]);
if (slaveAddress == 0)
{
return QByteArray();
}
if (m_slaveId != 0 && slaveAddress != static_cast<quint8>(m_slaveId))
{
return QByteArray();
}
// 路由到对应功能处理器
QByteArray responsePdu = routeFunctionCode(functionCode, pdu);
if (responsePdu.isEmpty())
{
return QByteArray();
}
// 构建响应ADU(应用数据单元:从站地址 + PDU + CRC)
QByteArray responseAdu;
responseAdu.append(static_cast<char>(slaveAddress));
responseAdu.append(responsePdu);
// 计算并添加CRC校验码
quint16 crc = calculateCRC(responseAdu);
quint16 crcLe = qToLittleEndian(crc);
responseAdu.append(reinterpret_cast<const char *>(&crcLe), 2);
emit packetSent(formatPacket(responseAdu, "→ RTU发送"));
return responseAdu;
}
void ModbusServer::setSlaveId(int slaveId)
{
if (slaveId < 0)
{
return;
}
if (slaveId > 247)
{
slaveId = 247;
}
if (m_slaveId == slaveId)
{
return;
}
m_slaveId = slaveId;
emit slaveIdChanged(m_slaveId);
}
quint16 ModbusServer::calculateCRC(const QByteArray &data)
{
quint16 crc = 0xFFFF;
for (int i = 0; i < data.size(); ++i)
{
crc ^= static_cast<quint8>(data[i]);
for (int j = 0; j < 8; ++j)
{
if (crc & 0x0001)
{
crc = (crc >> 1) ^ 0xA001;
}
else
{
crc >>= 1;
}
}
}
return crc;
}
// ========== 通用控制 ==========
void ModbusServer::stop()
{
stopTcp();
stopRtu();
m_running = false;
setStatusMessage("服务器已停止");
emit runningChanged(false);
}
void ModbusServer::initializeData()
{
// 初始化线圈
m_dataStore->initializeCoils(0, 100, false);
// 初始化离散输入
m_dataStore->initializeDiscreteInputs(0, 100, false);
// 初始化保持寄存器
m_dataStore->initializeHoldingRegisters(0, 100, 0);
// 初始化输入寄存器
m_dataStore->initializeInputRegisters(0, 100, 0);
// 创建测试文件
// m_fileStore->createFile(1, "温度数据文件", 256);
// m_fileStore->createFile(2, "状态数据文件", 128);
// 初始化保持寄存器(文件)地址存储
m_addressStore->initializeRegion(1000, 200);
qDebug() << "数据初始化完成";
}
// 获取文件列表
QStringList ModbusServer::getFileList() const
{
return m_fileStore->getFileList();
}
bool ModbusServer::importFileFromLocal(int fileNumber, const QString &path)
{
if (!m_fileStore || fileNumber < 0 || fileNumber > 65535)
{
qWarning() << "[ModbusServer] 导入失败: 文件号非法" << fileNumber;
return false;
}
QString localPath = QUrl(path).toLocalFile();
if (localPath.isEmpty())
{
localPath = path;
}
QFile inputFile(localPath);
if (!inputFile.open(QIODevice::ReadOnly))
{
qWarning() << "[ModbusServer] 导入失败: 无法打开文件" << localPath;
return false;
}
const QByteArray fileBytes = inputFile.readAll();
inputFile.close();
// 先清理同名文件,避免旧记录残留。
m_fileStore->clearFileRecords(static_cast<quint16>(fileNumber));
if (fileBytes.isEmpty())
{
return m_fileStore->createFile(static_cast<quint16>(fileNumber), "导入的空文件", 10000);
}
const int totalRecords = (fileBytes.size() + 1) / 2;
if (totalRecords > 10000)
{
qWarning() << "[ModbusServer] 导入失败: 文件过大,记录数超过上限10000" << totalRecords;
return false;
}
int offset = 0;
quint16 recordNumber = 0;
while (offset < fileBytes.size())
{
const int chunkRecords = qMin(126, totalRecords - static_cast<int>(recordNumber));
QByteArray chunkData;
chunkData.reserve(chunkRecords * 2);
for (int i = 0; i < chunkRecords; ++i)
{
if (offset < fileBytes.size())
{
chunkData.append(fileBytes.at(offset++));
}
else
{
chunkData.append('\0');
}
if (offset < fileBytes.size())
{
chunkData.append(fileBytes.at(offset++));
}
else
{
chunkData.append('\0');
}
}
QByteArray request;
request.append(static_cast<char>(0x15)); // FC 21
request.append(static_cast<char>(7 + chunkData.size())); // ByteCount
request.append(static_cast<char>(0x06)); // Reference Type
request.append(static_cast<char>((fileNumber >> 8) & 0xFF)); // File Number Hi
request.append(static_cast<char>(fileNumber & 0xFF)); // File Number Lo
request.append(static_cast<char>((recordNumber >> 8) & 0xFF)); // Record Number Hi
request.append(static_cast<char>(recordNumber & 0xFF)); // Record Number Lo
request.append(static_cast<char>((chunkRecords >> 8) & 0xFF)); // Record Length Hi
request.append(static_cast<char>(chunkRecords & 0xFF)); // Record Length Lo
request.append(chunkData); // Data
const QByteArray response = m_fileStore->handleWriteFileRecord(request);
if (response.size() < 2 || static_cast<quint8>(response.at(0)) == 0x95)
{
qWarning() << "[ModbusServer] 导入失败: FileStore 写入异常,record=" << recordNumber;
return false;
}
recordNumber = static_cast<quint16>(recordNumber + chunkRecords);
}
qDebug() << "[ModbusServer] 导入成功: 文件号" << fileNumber << "字节数" << fileBytes.size() << "记录数" << totalRecords;
return true;
}
bool ModbusServer::exportFileToLocal(int fileNumber, const QString &path)
{
if (!m_fileStore || fileNumber < 0 || fileNumber > 65535)
{
qWarning() << "[ModbusServer] 导出失败: 文件号非法" << fileNumber;
return false;
}
const QMap<quint16, QByteArray> records = m_fileStore->getAllRecordsRaw(static_cast<quint16>(fileNumber), 10000);
if (records.isEmpty())
{
qWarning() << "[ModbusServer] 导出失败: 文件无数据" << fileNumber;
return false;
}
quint16 maxRecordNumber = 0;
for (auto it = records.begin(); it != records.end(); ++it)
{
if (it.key() > maxRecordNumber)
{
maxRecordNumber = it.key();
}
}
QByteArray outputData;
outputData.reserve((maxRecordNumber + 1) * 2);
for (quint16 recordNum = 0; recordNum <= maxRecordNumber; ++recordNum)
{
QByteArray recordBytes = records.value(recordNum, QByteArray(2, '\0'));
if (recordBytes.size() < 2)
{
recordBytes = recordBytes.leftJustified(2, '\0', true);
}
outputData.append(recordBytes.left(2));
}
QString localPath = QUrl(path).toLocalFile();
if (localPath.isEmpty())
{
localPath = path;
}
QFile outputFile(localPath);
if (!outputFile.open(QIODevice::WriteOnly | QIODevice::Truncate))
{
qWarning() << "[ModbusServer] 导出失败: 无法写入文件" << localPath;
return false;
}
const qint64 written = outputFile.write(outputData);
outputFile.close();
if (written != outputData.size())
{
qWarning() << "[ModbusServer] 导出失败: 写入字节数不完整,expect=" << outputData.size() << "actual=" << written;
return false;
}
qDebug() << "[ModbusServer] 导出成功: 文件号" << fileNumber << "输出字节数" << outputData.size();
return true;
}
bool ModbusServer::importHoldingRegistersFromLocal(int startAddress, int maxCount, const QString &path)
{
if (!m_dataStore || startAddress < 0 || startAddress >= ModbusDataStore::MAX_HOLDING_REGISTER_ADDRESS)
{
qWarning() << "[ModbusServer] 导入保持寄存器失败: 起始地址非法" << startAddress;
return false;
}
if (maxCount <= 0)
{
qWarning() << "[ModbusServer] 导入保持寄存器失败: 数量非法" << maxCount;
return false;
}
QString localPath = QUrl(path).toLocalFile();
if (localPath.isEmpty())
{
localPath = path;
}
QFile inputFile(localPath);
if (!inputFile.open(QIODevice::ReadOnly))
{
qWarning() << "[ModbusServer] 导入保持寄存器失败: 无法打开文件" << localPath;
return false;
}
const QByteArray fileBytes = inputFile.readAll();
inputFile.close();
if (fileBytes.isEmpty())
{
qWarning() << "[ModbusServer] 导入保持寄存器失败: 文件为空";
return false;
}
const int registerCountFromFile = (fileBytes.size() + 1) / 2;
const int writableCount = qMin(registerCountFromFile, maxCount);
const int endAddress = startAddress + writableCount - 1;
if (endAddress >= ModbusDataStore::MAX_HOLDING_REGISTER_ADDRESS)
{
qWarning() << "[ModbusServer] 导入保持寄存器失败: 地址范围越界" << startAddress << "-" << endAddress;
return false;
}
int offset = 0;
for (int i = 0; i < writableCount; ++i)
{
const quint8 high = offset < fileBytes.size() ? static_cast<quint8>(fileBytes.at(offset++)) : 0;
const quint8 low = offset < fileBytes.size() ? static_cast<quint8>(fileBytes.at(offset++)) : 0;
const quint16 value = static_cast<quint16>((high << 8) | low);
if (!m_dataStore->writeHoldingRegister(static_cast<quint16>(startAddress + i), value))
{
qWarning() << "[ModbusServer] 导入保持寄存器失败: 写入地址失败" << (startAddress + i);
return false;
}
}
qDebug() << "[ModbusServer] 导入保持寄存器成功: 起始地址" << startAddress
<< "写入数量" << writableCount << "文件寄存器数量" << registerCountFromFile;
return true;
}
bool ModbusServer::exportHoldingRegistersToLocal(int startAddress, int count, const QString &path)
{
if (!m_dataStore || count <= 0 || startAddress < 0 || startAddress >= ModbusDataStore::MAX_HOLDING_REGISTER_ADDRESS)
{
qWarning() << "[ModbusServer] 导出保持寄存器失败: 参数非法" << startAddress << count;
return false;
}
const int endAddress = startAddress + count - 1;
if (endAddress >= ModbusDataStore::MAX_HOLDING_REGISTER_ADDRESS)
{
qWarning() << "[ModbusServer] 导出保持寄存器失败: 地址范围越界" << startAddress << "-" << endAddress;
return false;
}
QByteArray outputData;
outputData.reserve(count * 2);
for (int i = 0; i < count; ++i)
{
const quint16 value = m_dataStore->readHoldingRegister(static_cast<quint16>(startAddress + i));
outputData.append(static_cast<char>((value >> 8) & 0xFF));
outputData.append(static_cast<char>(value & 0xFF));
}
QString localPath = QUrl(path).toLocalFile();
if (localPath.isEmpty())
{
localPath = path;
}
QFile outputFile(localPath);
if (!outputFile.open(QIODevice::WriteOnly | QIODevice::Truncate))
{
qWarning() << "[ModbusServer] 导出保持寄存器失败: 无法写入文件" << localPath;
return false;
}
const qint64 written = outputFile.write(outputData);
outputFile.close();
if (written != outputData.size())
{
qWarning() << "[ModbusServer] 导出保持寄存器失败: 写入字节数不完整,expect=" << outputData.size() << "actual=" << written;
return false;
}
qDebug() << "[ModbusServer] 导出保持寄存器成功: 起始地址" << startAddress << "数量" << count;
return true;
}
// 查询文件内容
QString ModbusServer::queryFileContent(int fileNumber, int maxRecords)
{
QString result;
result += QString("========== 文件 %1 内容查询 ==========\n\n").arg(fileNumber);
// 获取文件信息
QString info = m_fileStore->getFileInfo(fileNumber);
result += info + "\n\n";
// 获取所有记录(原始字节数据)
QMap<quint16, QByteArray> records = m_fileStore->getAllRecordsRaw(fileNumber, maxRecords);
if (records.isEmpty())
{
result += "该文件暂无数据写入\n";
}
else
{
result += QString("已写入的记录(最多显示 %1 条):\n").arg(maxRecords);
result += "记录号 十六进制 ASCII字符串\n";
result += "------ -------------------------------- ----------------------\n";
for (auto it = records.begin(); it != records.end(); ++it)
{
quint16 recordNum = it.key();
QByteArray data = it.value();
// 十六进制显示
QString hexStr;
for (int i = 0; i < data.size(); ++i)
{
hexStr += QString("%1 ").arg((unsigned char)data[i], 2, 16, QChar('0')).toUpper();
}
hexStr = hexStr.leftJustified(36, ' ');
// ASCII显示(可打印字符)
QString asciiStr;
for (int i = 0; i < data.size(); ++i)
{
char c = data[i];
if (c >= 32 && c <= 126)
{ // 可打印字符
asciiStr += c;
}
else
{
asciiStr += '.';
}
}
result += QString("%1 %2 %3\n")
.arg(recordNum, 6)
.arg(hexStr)
.arg(asciiStr);
}
result += QString("\n总计: %1 条记录\n").arg(records.size());
}
result += "\n" + QString("=").repeated(40) + "\n";
return result;
}
QString ModbusServer::queryAddressFile(int startAddress, int count)
{
QString result;
result += QString("========== 保持寄存器查询 ==========\n\n");
result += QString("起始地址: %1\n").arg(startAddress);
result += QString("查询数量: %1\n\n").arg(count);
// 从ModbusDataStore读取保持寄存器
QMap<quint16, QByteArray> data;
bool hasData = false;
for (int i = 0; i < count; ++i)
{
quint16 addr = startAddress + i;
quint16 value = m_dataStore->readHoldingRegister(addr);
// 将16位值转换为大端字节序的2字节数据
QByteArray bytes(2, 0);
bytes[0] = static_cast<char>((value >> 8) & 0xFF); // 高字节
bytes[1] = static_cast<char>(value & 0xFF); // 低字节
// 只记录非零的寄存器
if (value != 0)
{
hasData = true;
}
data[addr] = bytes;
}
if (!hasData)
{
result += "该地址区域暂无数据写入(所有值为0)\n";
}
else
{
result += QString("保持寄存器数据(显示 %1 个地址):\n").arg(count);
result += "地址 十进制值 十六进制 ASCII字符串\n";
result += "------ -------- -------------------------------- ----------------------\n";
for (auto it = data.begin(); it != data.end(); ++it)
{
quint16 address = it.key();
QByteArray bytes = it.value();
// 转换回16位值用于十进制显示
quint16 value = ((unsigned char)bytes[0] << 8) | (unsigned char)bytes[1];
// 十六进制显示
QString hexStr;
for (int i = 0; i < bytes.size(); ++i)
{
hexStr += QString("%1 ").arg((unsigned char)bytes[i], 2, 16, QChar('0')).toUpper();
}
hexStr = hexStr.leftJustified(34, ' ');
// ASCII显示(可打印字符)
QString asciiStr;
for (int i = 0; i < bytes.size(); ++i)
{
char c = bytes[i];
if (c >= 32 && c <= 126)
{ // 可打印字符
asciiStr += c;
}
else
{
asciiStr += '.';
}
}
result += QString("%1 %2 %3 %4\n")
.arg(address, 6)
.arg(value, 8)
.arg(hexStr)
.arg(asciiStr);
}
// 统计非零值数量
int nonZeroCount = 0;
for (auto it = data.begin(); it != data.end(); ++it)
{
quint16 value = ((unsigned char)it.value()[0] << 8) | (unsigned char)it.value()[1];
if (value != 0)
nonZeroCount++;
}
result += QString("\n总计: %1 个地址,其中 %2 个非零值\n").arg(data.size()).arg(nonZeroCount);
}
result += "\n" + QString("=").repeated(40) + "\n";
return result;
}
// ========== 功能码路由 ==========
QByteArray ModbusServer::routeFunctionCode(quint8 functionCode, const QByteArray &pdu)
{
QByteArray response;
switch (functionCode)
{
// 标准Modbus功能码:01-16
case ReadCoils:
case ReadDiscreteInputs:
case ReadHoldingRegisters:
case ReadInputRegisters:
case WriteSingleCoil:
case WriteSingleRegister:
case WriteMultipleCoils:
case WriteMultipleRegisters:
response = m_functionHandler->processRequest(pdu);
break;
// 文件记录操作:20-21
case ReadFileRecord:
response = m_fileStore->handleReadFileRecord(pdu);
break;
case WriteFileRecord:
response = m_fileStore->handleWriteFileRecord(pdu);
break;
// 自定义文件操作:203-204
case ReadFile:
response = m_addressStore->handleReadFile(pdu);
break;
case WriteFile:
response = m_addressStore->handleWriteFile(pdu);
break;
default:
// 返回非法功能错误
response.append(static_cast<char>(functionCode | 0x80));
response.append(static_cast<char>(IllegalFunction));
break;
}
if (response.isEmpty())
{
qWarning() << "功能码" << functionCode << "处理失败,返回空响应";
}
return response;
}
int ModbusServer::getExpectedFrameLength(quint8 functionCode, const QByteArray &buffer)
{
// RTU帧结构:从站地址(1) + 功能码(1) + 数据(N) + CRC(2)
int minLength = 4; // 最小长度
if (buffer.size() < 3)
{
return -1; // 数据不足,无法判断
}
switch (functionCode)
{
case ReadCoils: // 0x01
case ReadDiscreteInputs: // 0x02