6 Commits 3ccb629559 ... a42d9f13d2

Author SHA1 Message Date
  tom a42d9f13d2 提交新485布局变更。 2 months ago
  tom af2559d8f1 多socket连接方案改版。 2 months ago
  tom b920d62c46 保存old socket。 2 months ago
  tom 4ce4c5b98c 更改seat表和sim表数据。 2 months ago
  tom ce26747ef5 暂时设置间隔时间统一为300ms。 2 months ago
  tom 23182ec2e4 处理报文前端加00的情况,最多可能加5组00。调试模式下,两个清除按钮,都删除DebugFault表所有数据。 2 months ago
31 changed files with 1388 additions and 716 deletions
  1. 16 15
      pla-sim/01_SQL/02_table/mx_seat.sql
  2. 32 32
      pla-sim/01_SQL/02_table/mx_sim.sql
  3. 16 15
      pla-sim/01_SQL/02_table/sim_seat.sql
  4. 2 2
      pla-sim/01_SQL/02_table/sim_sim.sql
  5. 2 1
      ruoyi-admin/src/main/resources/application-druid.yml
  6. 6 3
      ruoyi-admin/src/main/resources/application.yml
  7. 24 4
      ruoyi-sim/src/main/java/com/ruoyi/sim/constant/CommConst.java
  8. 41 26
      ruoyi-sim/src/main/java/com/ruoyi/sim/controller/HardwareCommDebugController.java
  9. 8 13
      ruoyi-sim/src/main/java/com/ruoyi/sim/controller/RealExamController.java
  10. 18 10
      ruoyi-sim/src/main/java/com/ruoyi/sim/controller/SeatController.java
  11. 36 37
      ruoyi-sim/src/main/java/com/ruoyi/sim/controller/TestIotController.java
  12. 17 13
      ruoyi-sim/src/main/java/com/ruoyi/sim/domain/RealExam.java
  13. 46 15
      ruoyi-sim/src/main/java/com/ruoyi/sim/domain/Seat.java
  14. 4 0
      ruoyi-sim/src/main/java/com/ruoyi/sim/domain/Sim.java
  15. 40 27
      ruoyi-sim/src/main/java/com/ruoyi/sim/domain/SimMsg.java
  16. 40 3
      ruoyi-sim/src/main/java/com/ruoyi/sim/domain/vo/SimSocketVo.java
  17. 8 8
      ruoyi-sim/src/main/java/com/ruoyi/sim/domain/vo/SocketWrapValue.java
  18. 23 9
      ruoyi-sim/src/main/java/com/ruoyi/sim/service/impl/CommBuildService.java
  19. 301 0
      ruoyi-sim/src/main/java/com/ruoyi/sim/service/impl/CommCheckService.java
  20. 28 0
      ruoyi-sim/src/main/java/com/ruoyi/sim/service/impl/CommParseUtils.java
  21. 27 88
      ruoyi-sim/src/main/java/com/ruoyi/sim/service/impl/CommReceiveService.java
  22. 277 291
      ruoyi-sim/src/main/java/com/ruoyi/sim/service/impl/CommSendService.java
  23. 0 2
      ruoyi-sim/src/main/java/com/ruoyi/sim/service/impl/DebugFaultService.java
  24. 20 4
      ruoyi-sim/src/main/java/com/ruoyi/sim/service/impl/FailedCountService.java
  25. 12 6
      ruoyi-sim/src/main/java/com/ruoyi/sim/service/impl/RealExamCollectionService.java
  26. 110 23
      ruoyi-sim/src/main/java/com/ruoyi/sim/service/impl/RealExamService.java
  27. 111 1
      ruoyi-sim/src/main/java/com/ruoyi/sim/service/impl/SeatService.java
  28. 18 8
      ruoyi-sim/src/main/java/com/ruoyi/sim/service/impl/SimService.java
  29. 87 58
      ruoyi-sim/src/main/java/com/ruoyi/sim/service/impl/SocketService.java
  30. 7 1
      ruoyi-sim/src/main/resources/mapper/sim/RealExamMapper.xml
  31. 11 1
      ruoyi-sim/src/main/resources/mapper/sim/SeatMapper.xml

+ 16 - 15
pla-sim/01_SQL/02_table/mx_seat.sql

@@ -11,7 +11,7 @@
  Target Server Version : 80020 (8.0.20)
  File Encoding         : 65001
 
- Date: 11/03/2025 17:09:20
+ Date: 12/03/2025 16:30:24
 */
 
 SET NAMES utf8mb4;
@@ -24,11 +24,12 @@ DROP TABLE IF EXISTS `mx_seat`;
 CREATE TABLE `mx_seat`  (
   `seat_id` bigint NOT NULL AUTO_INCREMENT COMMENT '座ID',
   `seat_num` int NOT NULL DEFAULT 0 COMMENT '座号',
-  `seat_bind_ip` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '学员端座次上[电脑]绑定的[IP地址]',
-  `seat_rs485_ip` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '学员端座次上[RS485]绑定的[IP地址]',
-  `seat_rs485_port` int NOT NULL COMMENT '学员端座次上[RS485]绑定的[端口]',
+  `seat_bind_ip` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '[电脑]绑定的[IP地址]',
+  `seat_rs485_ip` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '[RS485]绑定的[IP地址]',
+  `seat_rs485_port` int NOT NULL COMMENT '[RS485]绑定的[端口]',
+  `seat_rs485_socket_state` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'Socket状态:[0]:初始化,[1]:打开,[2]:关闭,[5]:禁用',
   `current_user_id` bigint NOT NULL DEFAULT 0 COMMENT '当前座上学员/用户ID',
-  `current_sim_id` bigint NOT NULL DEFAULT 0 COMMENT '学员端座次上模拟器的ID:[0]没有连接任何模拟器',
+  `current_sim_id` bigint NOT NULL DEFAULT 0 COMMENT '模拟器的ID:[0]没有连接任何模拟器,[xx]:具体某台模拟器',
   `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '创建者',
   `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
   `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '更新者',
@@ -40,15 +41,15 @@ CREATE TABLE `mx_seat`  (
 -- ----------------------------
 -- Records of mx_seat
 -- ----------------------------
-INSERT INTO `mx_seat` VALUES (1, 1, '192.168.1.101', '192.168.1.201', 10001, 0, 0, NULL, NULL, NULL, NULL, '座号01');
-INSERT INTO `mx_seat` VALUES (2, 2, '192.168.1.102', '192.168.1.201', 10002, 0, 0, NULL, NULL, NULL, NULL, '座号02');
-INSERT INTO `mx_seat` VALUES (3, 3, '192.168.1.103', '192.168.1.201', 10003, 0, 0, NULL, NULL, NULL, NULL, '座号03');
-INSERT INTO `mx_seat` VALUES (4, 4, '192.168.1.104', '192.168.1.201', 10004, 0, 0, NULL, NULL, NULL, NULL, '座号04');
-INSERT INTO `mx_seat` VALUES (5, 5, '192.168.1.105', '192.168.1.201', 10008, 0, 0, NULL, NULL, NULL, NULL, '座号05');
-INSERT INTO `mx_seat` VALUES (6, 6, '192.168.1.106', '192.168.1.202', 11001, 0, 0, NULL, NULL, NULL, NULL, '座号06');
-INSERT INTO `mx_seat` VALUES (7, 7, '192.168.1.107', '192.168.1.202', 11002, 0, 0, NULL, NULL, NULL, NULL, '座号07');
-INSERT INTO `mx_seat` VALUES (8, 8, '192.168.1.108', '192.168.1.202', 11003, 0, 0, NULL, NULL, NULL, NULL, '座号08');
-INSERT INTO `mx_seat` VALUES (9, 9, '192.168.1.109', '192.168.1.202', 11004, 0, 0, NULL, NULL, NULL, NULL, '座号09');
-INSERT INTO `mx_seat` VALUES (10, 10, '192.168.1.110', '192.168.1.202', 11008, 0, 0, NULL, NULL, NULL, NULL, '座号10');
+INSERT INTO `mx_seat` VALUES (1, 1, '192.168.1.101', '192.168.1.201', 10001, '', 0, 0, NULL, NULL, NULL, NULL, '座号01');
+INSERT INTO `mx_seat` VALUES (2, 2, '192.168.1.102', '192.168.1.201', 10002, '', 0, 0, NULL, NULL, NULL, NULL, '座号02');
+INSERT INTO `mx_seat` VALUES (3, 3, '192.168.1.103', '192.168.1.201', 10003, '', 0, 0, NULL, NULL, NULL, NULL, '座号03');
+INSERT INTO `mx_seat` VALUES (4, 4, '192.168.1.104', '192.168.1.201', 10004, '', 0, 0, NULL, NULL, NULL, NULL, '座号04');
+INSERT INTO `mx_seat` VALUES (5, 5, '192.168.1.105', '192.168.1.201', 10008, '', 0, 0, NULL, NULL, NULL, NULL, '座号05');
+INSERT INTO `mx_seat` VALUES (6, 6, '192.168.1.106', '192.168.1.202', 11001, '', 0, 0, NULL, NULL, NULL, NULL, '座号06');
+INSERT INTO `mx_seat` VALUES (7, 7, '192.168.1.107', '192.168.1.202', 11002, '', 0, 0, NULL, NULL, NULL, NULL, '座号07');
+INSERT INTO `mx_seat` VALUES (8, 8, '192.168.1.108', '192.168.1.202', 11003, '', 0, 0, NULL, NULL, NULL, NULL, '座号08');
+INSERT INTO `mx_seat` VALUES (9, 9, '192.168.1.109', '192.168.1.202', 11004, '', 0, 0, NULL, NULL, NULL, NULL, '座号09');
+INSERT INTO `mx_seat` VALUES (10, 10, '192.168.1.110', '192.168.1.202', 11008, '', 0, 0, NULL, NULL, NULL, NULL, '座号10');
 
 SET FOREIGN_KEY_CHECKS = 1;

+ 32 - 32
pla-sim/01_SQL/02_table/mx_sim.sql

@@ -11,7 +11,7 @@
  Target Server Version : 80020 (8.0.20)
  File Encoding         : 65001
 
- Date: 11/03/2025 17:09:15
+ Date: 12/03/2025 16:30:14
 */
 
 SET NAMES utf8mb4;
@@ -25,7 +25,7 @@ CREATE TABLE `mx_sim`  (
   `sim_id` bigint NOT NULL AUTO_INCREMENT COMMENT '模拟器ID',
   `seat_id` bigint NOT NULL DEFAULT 0 COMMENT '[废弃]座ID',
   `sim_type` char(4) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '模拟器类型',
-  `sim_state` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '0' COMMENT '模拟器状态:[0]:可用初始化,[1]:在线,[2]:模拟器离线,[3]:网关离线,[4]:硬件故障异常,[5]:手动禁用',
+  `sim_state` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '0' COMMENT '模拟器状态:[0]:初始化,[1]:在线,[2]:离线,[3]:[废弃]网关离线,[4]:[废弃]硬件故障异常,[5]:手动禁用',
   `sim_sn` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '模拟器序列号',
   `sim_num` char(2) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '模拟器设备通信编号-站ID',
   `last_sent_time` datetime NULL DEFAULT NULL COMMENT '最后一次成功发送报文时间',
@@ -41,35 +41,35 @@ CREATE TABLE `mx_sim`  (
 -- ----------------------------
 -- Records of mx_sim
 -- ----------------------------
-INSERT INTO `mx_sim` VALUES (1, 1, '0001', '2', '', '01', NULL, NULL, NULL, NULL, NULL, '2025-03-11 17:08:50', '');
-INSERT INTO `mx_sim` VALUES (2, 2, '0001', '2', '', '02', NULL, NULL, NULL, NULL, NULL, '2025-03-11 17:08:50', '');
-INSERT INTO `mx_sim` VALUES (3, 3, '0001', '2', '', '03', NULL, NULL, NULL, NULL, NULL, '2025-03-11 17:08:50', '');
-INSERT INTO `mx_sim` VALUES (4, 4, '0001', '2', '', '04', NULL, NULL, NULL, NULL, NULL, '2025-03-11 17:08:50', '');
-INSERT INTO `mx_sim` VALUES (5, 5, '0001', '2', '', '05', NULL, NULL, NULL, NULL, NULL, '2025-03-11 17:08:51', '');
-INSERT INTO `mx_sim` VALUES (6, 6, '0001', '2', '', '06', NULL, NULL, NULL, NULL, NULL, '2025-03-11 17:08:51', '');
-INSERT INTO `mx_sim` VALUES (7, 7, '0001', '2', '', '07', NULL, NULL, NULL, NULL, NULL, '2025-03-11 17:08:51', '');
-INSERT INTO `mx_sim` VALUES (8, 8, '0001', '2', '', '08', NULL, NULL, NULL, NULL, NULL, '2025-03-11 17:08:31', '');
-INSERT INTO `mx_sim` VALUES (9, 9, '0001', '2', '', '09', NULL, NULL, NULL, NULL, NULL, '2025-03-11 17:08:51', '');
-INSERT INTO `mx_sim` VALUES (10, 10, '0001', '2', '', '10', NULL, NULL, NULL, NULL, NULL, '2025-03-11 17:08:51', '');
-INSERT INTO `mx_sim` VALUES (81, 1, '0002', '2', '', '51', NULL, NULL, NULL, NULL, NULL, '2025-03-11 17:08:31', '');
-INSERT INTO `mx_sim` VALUES (82, 2, '0002', '2', '', '52', NULL, NULL, NULL, NULL, NULL, '2025-03-11 17:08:51', '');
-INSERT INTO `mx_sim` VALUES (83, 3, '0002', '2', '', '53', NULL, NULL, NULL, NULL, NULL, '2025-03-11 17:08:51', '');
-INSERT INTO `mx_sim` VALUES (84, 4, '0002', '2', '', '54', NULL, NULL, NULL, NULL, NULL, '2025-03-11 17:08:52', '');
-INSERT INTO `mx_sim` VALUES (85, 5, '0002', '2', '', '55', NULL, NULL, NULL, NULL, NULL, '2025-03-11 17:08:52', '');
-INSERT INTO `mx_sim` VALUES (86, 6, '0002', '2', '', '56', NULL, NULL, NULL, NULL, NULL, '2025-03-11 17:08:52', '');
-INSERT INTO `mx_sim` VALUES (87, 7, '0002', '2', '', '57', NULL, NULL, NULL, NULL, NULL, '2025-03-11 17:08:52', '');
-INSERT INTO `mx_sim` VALUES (88, 8, '0002', '2', '', '58', NULL, NULL, NULL, NULL, NULL, '2025-03-11 17:08:52', '');
-INSERT INTO `mx_sim` VALUES (89, 9, '0002', '2', '', '59', NULL, NULL, NULL, NULL, NULL, '2025-03-11 17:08:52', '');
-INSERT INTO `mx_sim` VALUES (90, 10, '0002', '2', '', '5A', NULL, NULL, NULL, NULL, NULL, '2025-03-11 17:08:52', '');
-INSERT INTO `mx_sim` VALUES (161, 1, '0003', '2', '', 'A1', NULL, NULL, NULL, NULL, NULL, '2025-03-11 17:08:52', '');
-INSERT INTO `mx_sim` VALUES (162, 2, '0003', '2', '', 'A2', NULL, NULL, NULL, NULL, NULL, '2025-03-11 17:08:52', '');
-INSERT INTO `mx_sim` VALUES (163, 3, '0003', '2', '', 'A3', NULL, NULL, NULL, NULL, NULL, '2025-03-11 17:08:52', '');
-INSERT INTO `mx_sim` VALUES (164, 4, '0003', '2', '', 'A4', NULL, NULL, NULL, NULL, NULL, '2025-03-11 17:08:53', '');
-INSERT INTO `mx_sim` VALUES (165, 5, '0003', '2', '', 'A5', NULL, NULL, NULL, NULL, NULL, '2025-03-11 17:08:53', '');
-INSERT INTO `mx_sim` VALUES (166, 6, '0003', '2', '', 'A6', NULL, NULL, NULL, NULL, NULL, '2025-03-11 17:08:53', '');
-INSERT INTO `mx_sim` VALUES (167, 7, '0003', '0', '', 'A7', NULL, NULL, NULL, NULL, NULL, NULL, '');
-INSERT INTO `mx_sim` VALUES (168, 8, '0003', '2', '', 'A8', NULL, NULL, NULL, NULL, NULL, '2025-03-11 17:08:53', '');
-INSERT INTO `mx_sim` VALUES (169, 9, '0003', '2', '', 'A9', NULL, NULL, NULL, NULL, NULL, '2025-03-11 17:08:53', '');
-INSERT INTO `mx_sim` VALUES (170, 10, '0003', '2', '', 'AA', NULL, NULL, NULL, NULL, NULL, '2025-03-11 17:08:53', '');
+INSERT INTO `mx_sim` VALUES (1, 1, '0001', '2', '', '01', NULL, NULL, NULL, NULL, NULL, '2025-03-12 16:29:50', '');
+INSERT INTO `mx_sim` VALUES (2, 2, '0001', '2', '', '02', NULL, NULL, NULL, NULL, NULL, '2025-03-12 16:29:50', '');
+INSERT INTO `mx_sim` VALUES (3, 3, '0001', '2', '', '03', NULL, NULL, NULL, NULL, NULL, '2025-03-12 16:29:50', '');
+INSERT INTO `mx_sim` VALUES (4, 4, '0001', '2', '', '04', NULL, NULL, NULL, NULL, NULL, '2025-03-12 16:29:50', '');
+INSERT INTO `mx_sim` VALUES (5, 5, '0001', '2', '', '05', NULL, NULL, NULL, NULL, NULL, '2025-03-12 16:29:51', '');
+INSERT INTO `mx_sim` VALUES (6, 6, '0001', '2', '', '06', NULL, NULL, NULL, NULL, NULL, '2025-03-12 16:29:51', '');
+INSERT INTO `mx_sim` VALUES (7, 7, '0001', '2', '', '07', NULL, NULL, NULL, NULL, NULL, '2025-03-12 16:29:51', '');
+INSERT INTO `mx_sim` VALUES (8, 8, '0001', '2', '', '08', NULL, NULL, NULL, NULL, NULL, '2025-03-12 16:29:31', '');
+INSERT INTO `mx_sim` VALUES (9, 9, '0001', '2', '', '09', NULL, NULL, NULL, NULL, NULL, '2025-03-12 16:29:51', '');
+INSERT INTO `mx_sim` VALUES (10, 10, '0001', '2', '', '10', NULL, NULL, NULL, NULL, NULL, '2025-03-12 16:29:51', '');
+INSERT INTO `mx_sim` VALUES (81, 1, '0002', '2', '', '51', NULL, NULL, NULL, NULL, NULL, '2025-03-12 16:29:31', '');
+INSERT INTO `mx_sim` VALUES (82, 2, '0002', '2', '', '52', NULL, NULL, NULL, NULL, NULL, '2025-03-12 16:29:51', '');
+INSERT INTO `mx_sim` VALUES (83, 3, '0002', '2', '', '53', NULL, NULL, NULL, NULL, NULL, '2025-03-12 16:29:51', '');
+INSERT INTO `mx_sim` VALUES (84, 4, '0002', '2', '', '54', NULL, NULL, NULL, NULL, NULL, '2025-03-12 16:29:52', '');
+INSERT INTO `mx_sim` VALUES (85, 5, '0002', '2', '', '55', NULL, NULL, NULL, NULL, NULL, '2025-03-12 16:29:52', '');
+INSERT INTO `mx_sim` VALUES (86, 6, '0002', '2', '', '56', NULL, NULL, NULL, NULL, NULL, '2025-03-12 16:29:52', '');
+INSERT INTO `mx_sim` VALUES (87, 7, '0002', '2', '', '57', NULL, NULL, NULL, NULL, NULL, '2025-03-12 16:29:52', '');
+INSERT INTO `mx_sim` VALUES (88, 8, '0002', '2', '', '58', NULL, NULL, NULL, NULL, NULL, '2025-03-12 16:29:52', '');
+INSERT INTO `mx_sim` VALUES (89, 9, '0002', '2', '', '59', NULL, NULL, NULL, NULL, NULL, '2025-03-12 16:29:52', '');
+INSERT INTO `mx_sim` VALUES (90, 10, '0002', '2', '', '5A', NULL, NULL, NULL, NULL, NULL, '2025-03-12 16:29:52', '');
+INSERT INTO `mx_sim` VALUES (161, 1, '0003', '2', '', 'A1', NULL, NULL, NULL, NULL, NULL, '2025-03-12 16:29:22', '');
+INSERT INTO `mx_sim` VALUES (162, 2, '0003', '2', '', 'A2', NULL, NULL, NULL, NULL, NULL, '2025-03-12 16:29:52', '');
+INSERT INTO `mx_sim` VALUES (163, 3, '0003', '2', '', 'A3', NULL, NULL, NULL, NULL, NULL, '2025-03-12 16:29:52', '');
+INSERT INTO `mx_sim` VALUES (164, 4, '0003', '2', '', 'A4', NULL, NULL, NULL, NULL, NULL, '2025-03-12 16:29:53', '');
+INSERT INTO `mx_sim` VALUES (165, 5, '0003', '2', '', 'A5', NULL, NULL, NULL, NULL, NULL, '2025-03-12 16:29:53', '');
+INSERT INTO `mx_sim` VALUES (166, 6, '0003', '2', '', 'A6', NULL, NULL, NULL, NULL, NULL, '2025-03-12 16:29:53', '');
+INSERT INTO `mx_sim` VALUES (167, 7, '0003', '2', '', 'A7', NULL, NULL, NULL, NULL, NULL, '2025-03-12 16:29:23', '');
+INSERT INTO `mx_sim` VALUES (168, 8, '0003', '2', '', 'A8', NULL, NULL, NULL, NULL, NULL, '2025-03-12 16:29:53', '');
+INSERT INTO `mx_sim` VALUES (169, 9, '0003', '2', '', 'A9', NULL, NULL, NULL, NULL, NULL, '2025-03-12 16:29:53', '');
+INSERT INTO `mx_sim` VALUES (170, 10, '0003', '2', '', 'AA', NULL, NULL, NULL, NULL, NULL, '2025-03-12 16:29:53', '');
 
 SET FOREIGN_KEY_CHECKS = 1;

+ 16 - 15
pla-sim/01_SQL/02_table/sim_seat.sql

@@ -11,7 +11,7 @@
  Target Server Version : 80020 (8.0.20)
  File Encoding         : 65001
 
- Date: 11/03/2025 17:09:06
+ Date: 12/03/2025 16:29:56
 */
 
 SET NAMES utf8mb4;
@@ -24,11 +24,12 @@ DROP TABLE IF EXISTS `sim_seat`;
 CREATE TABLE `sim_seat`  (
   `seat_id` bigint NOT NULL AUTO_INCREMENT COMMENT '座ID',
   `seat_num` int NOT NULL DEFAULT 0 COMMENT '座号',
-  `seat_bind_ip` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '学员端座次上[电脑]绑定的[IP地址]',
-  `seat_rs485_ip` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '学员端座次上[RS485]绑定的[IP地址]',
-  `seat_rs485_port` int NOT NULL COMMENT '学员端座次上[RS485]绑定的[端口]',
+  `seat_bind_ip` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '[电脑]绑定的[IP地址]',
+  `seat_rs485_ip` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '[RS485]绑定的[IP地址]',
+  `seat_rs485_port` int NOT NULL COMMENT '[RS485]绑定的[端口]',
+  `seat_rs485_socket_state` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Socket状态:[0]:初始化,[1]:打开,[2]:关闭,[5]:禁用',
   `current_user_id` bigint NOT NULL DEFAULT 0 COMMENT '当前座上学员/用户ID',
-  `current_sim_id` bigint NOT NULL DEFAULT 0 COMMENT '学员端座次上模拟器的ID:[0]没有连接任何模拟器',
+  `current_sim_id` bigint NOT NULL DEFAULT 0 COMMENT '模拟器的ID:[0]没有连接任何模拟器,[xx]:具体某台模拟器',
   `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '创建者',
   `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
   `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '更新者',
@@ -40,15 +41,15 @@ CREATE TABLE `sim_seat`  (
 -- ----------------------------
 -- Records of sim_seat
 -- ----------------------------
-INSERT INTO `sim_seat` VALUES (1, 1, '192.168.1.101', '192.168.1.201', 10001, 0, 0, NULL, NULL, NULL, NULL, '座号01');
-INSERT INTO `sim_seat` VALUES (2, 2, '192.168.1.102', '192.168.1.201', 10002, 0, 0, NULL, NULL, NULL, NULL, '座号02');
-INSERT INTO `sim_seat` VALUES (3, 3, '192.168.1.103', '192.168.1.201', 10003, 0, 0, NULL, NULL, NULL, NULL, '座号03');
-INSERT INTO `sim_seat` VALUES (4, 4, '192.168.1.104', '192.168.1.201', 10004, 0, 0, NULL, NULL, NULL, NULL, '座号04');
-INSERT INTO `sim_seat` VALUES (5, 5, '192.168.1.105', '192.168.1.201', 10008, 0, 0, NULL, NULL, NULL, NULL, '座号05');
-INSERT INTO `sim_seat` VALUES (6, 6, '192.168.1.106', '192.168.1.202', 11001, 0, 0, NULL, NULL, NULL, NULL, '座号06');
-INSERT INTO `sim_seat` VALUES (7, 7, '192.168.1.107', '192.168.1.202', 11002, 0, 0, NULL, NULL, NULL, NULL, '座号07');
-INSERT INTO `sim_seat` VALUES (8, 8, '192.168.1.108', '192.168.1.202', 11003, 0, 0, NULL, NULL, NULL, NULL, '座号08');
-INSERT INTO `sim_seat` VALUES (9, 9, '192.168.1.109', '192.168.1.202', 11004, 0, 0, NULL, NULL, NULL, NULL, '座号09');
-INSERT INTO `sim_seat` VALUES (10, 10, '192.168.1.110', '192.168.1.202', 11008, 0, 0, NULL, NULL, NULL, NULL, '座号10');
+INSERT INTO `sim_seat` VALUES (1, 1, '192.168.1.101', '192.168.1.201', 10001, '0', 0, 0, NULL, NULL, NULL, NULL, '座号01');
+INSERT INTO `sim_seat` VALUES (2, 2, '192.168.1.102', '192.168.1.201', 10002, '0', 0, 0, NULL, NULL, NULL, NULL, '座号02');
+INSERT INTO `sim_seat` VALUES (3, 3, '192.168.1.103', '192.168.1.201', 10003, '0', 0, 0, NULL, NULL, NULL, NULL, '座号03');
+INSERT INTO `sim_seat` VALUES (4, 4, '192.168.1.104', '192.168.1.201', 10004, '0', 0, 0, NULL, NULL, NULL, NULL, '座号04');
+INSERT INTO `sim_seat` VALUES (5, 5, '192.168.1.105', '192.168.1.201', 10008, '0', 0, 0, NULL, NULL, NULL, NULL, '座号05');
+INSERT INTO `sim_seat` VALUES (6, 6, '192.168.1.106', '192.168.1.202', 11001, '0', 0, 0, NULL, NULL, NULL, NULL, '座号06');
+INSERT INTO `sim_seat` VALUES (7, 7, '192.168.1.107', '192.168.1.202', 11002, '0', 0, 0, NULL, NULL, NULL, NULL, '座号07');
+INSERT INTO `sim_seat` VALUES (8, 8, '192.168.1.108', '192.168.1.202', 11003, '0', 0, 0, NULL, NULL, NULL, NULL, '座号08');
+INSERT INTO `sim_seat` VALUES (9, 9, '192.168.1.109', '192.168.1.202', 11004, '0', 0, 0, NULL, NULL, NULL, NULL, '座号09');
+INSERT INTO `sim_seat` VALUES (10, 10, '192.168.1.110', '192.168.1.202', 11008, '0', 0, 0, NULL, NULL, NULL, NULL, '座号10');
 
 SET FOREIGN_KEY_CHECKS = 1;

+ 2 - 2
pla-sim/01_SQL/02_table/sim_sim.sql

@@ -11,7 +11,7 @@
  Target Server Version : 80020 (8.0.20)
  File Encoding         : 65001
 
- Date: 11/03/2025 17:09:00
+ Date: 12/03/2025 16:29:47
 */
 
 SET NAMES utf8mb4;
@@ -25,7 +25,7 @@ CREATE TABLE `sim_sim`  (
   `sim_id` bigint NOT NULL AUTO_INCREMENT COMMENT '模拟器ID',
   `seat_id` bigint NOT NULL DEFAULT 0 COMMENT '[废弃]座ID',
   `sim_type` char(4) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '模拟器类型',
-  `sim_state` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '0' COMMENT '模拟器状态:[0]:可用初始化,[1]:在线,[2]:模拟器离线,[3]:网关离线,[4]:硬件故障异常,[5]:手动禁用',
+  `sim_state` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '0' COMMENT '模拟器状态:[0]:初始化,[1]:在线,[2]:离线,[3]:[废弃]网关离线,[4]:[废弃]硬件故障异常,[5]:手动禁用',
   `sim_sn` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '模拟器序列号',
   `sim_num` char(2) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '模拟器设备通信编号-站ID',
   `last_sent_time` datetime NULL DEFAULT NULL COMMENT '最后一次成功发送报文时间',

+ 2 - 1
ruoyi-admin/src/main/resources/application-druid.yml

@@ -18,7 +18,8 @@ spring:
                 # password: 8M6ahN7BXsXXDccR
 
                 # server-现场实验室
-                url: jdbc:mysql://192.168.1.61:4886/pla-chem-sim-dev-1?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+                # url: jdbc:mysql://192.168.1.61:4886/pla-chem-sim-dev-1?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+                url: jdbc:mysql://192.168.1.61:4886/pla-chem-sim-dev-2?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
                 username: root
                 password: 7ZNo#9Arn3DFBN8N
 

+ 6 - 3
ruoyi-admin/src/main/resources/application.yml

@@ -86,7 +86,8 @@ spring:
     # server-阿里云47服务器内网
     # database: 2
     # server-其他
-    database: 0
+    # database: 0
+    database: 2
     # 密码
     # server-阿里云47
     # password: Z*eQ8xXK7ryYynFv
@@ -151,9 +152,11 @@ xss:
 # com.ruoyi.sim.config.SimConfig
 sim-module-config:
   # 123.112.16.165
-  routerIp: 192.168.1.199
+  # routerIp: 221.218.212.74
+  routerIp: 192.168.1.1
   # 123.112.16.165
-  rs485Ip: 192.168.1.199
+  # rs485Ip: 221.218.212.74
+  rs485Ip: 127.0.0.1
   #
   rs485Port: 8899
   #

+ 24 - 4
ruoyi-sim/src/main/java/com/ruoyi/sim/constant/CommConst.java

@@ -1,5 +1,10 @@
 package com.ruoyi.sim.constant;
 
+/**
+ * 雷电扩展坞MAC
+ * 192.168.1.153
+ * 00-E0-4C-68-01-AD
+ */
 public interface CommConst {
 
     String PREFIX = "AA";
@@ -37,6 +42,8 @@ public interface CommConst {
      * 设备类型读取
      */
     String CMD_READ_TYPE = "03";
+
+    String BLANK_SIM_NUM = "00";
     /**
      * 状态读取 RESISTANCE电阻值
      */
@@ -67,18 +74,23 @@ public interface CommConst {
 
     /**
      * 请求间隔睡眠时间-long
+     * default:200L
      */
-    Long SLEEP_LONG = 200L;
+    Long SLEEP_LONG = 1000L;
     /**
      * 请求间隔睡眠时间-mid
+     * default:100L
      */
-    Long SLEEP_MID = 100L;
+    Long SLEEP_MID = 1000L;
     /**
      * 请求间隔睡眠时间-short
+     * default:64L
      */
-    Long SLEEP_SHORT = 64L;
+    Long SLEEP_SHORT = 1000L;
+
+    int SOCKET_TIME_OUT = 50;
 
-    int SOCKET_TIME_OUT = 200;
+    int PING_TIME_OUT = 1000;
 
     String[] TYPE_1_BIND_MSG = new String[]{"01", "02", "03", "04", "05",
             "06", "07", "08", "09", "0A"};
@@ -95,6 +107,7 @@ public interface CommConst {
     int RETRY_COUNT_WRITE_ONE_FAULT = 2;
     int RETRY_COUNT_READ_ONE_RESISTANCE = 4;
     int RETRY_COUNT_QUERY_SN_IMPORTANT = 1;
+    int RETRY_COUNT_WHICH_SIM_IMPORTANT = 3;
     int RETRY_COUNT_0 = 0;
 
     int OFFLINE_LIMIT = 6;
@@ -105,4 +118,11 @@ public interface CommConst {
 
     Integer PORT_MIN = 1;
     Integer PORT_MAX = 65535;
+
+    public interface COMM_TYPE {
+        Integer SET = 1;
+        Integer CLEAR = 2;
+        Integer READ = 3;
+        Integer WHO = 4;
+    }
 }

+ 41 - 26
ruoyi-sim/src/main/java/com/ruoyi/sim/controller/HardwareCommDebugController.java

@@ -9,17 +9,22 @@ import io.swagger.annotations.ApiOperation;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.ApplicationContext;
 import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.context.annotation.Lazy;
 import org.springframework.web.bind.annotation.*;
 
 @RestController
 @RequestMapping("/sim/debug")
 @Api("硬件通信DebugController")
 public class HardwareCommDebugController extends BaseController {
+
     @Autowired
+    @Lazy
     private CommSendService commSendService;
     @Autowired
+    @Lazy
     private CommBuildService commBuildService;
     @Autowired
+    @Lazy
     private ApplicationContext applicationContext;
 
     @GetMapping(value = "/spring-boot-close")
@@ -28,42 +33,42 @@ public class HardwareCommDebugController extends BaseController {
         ((ConfigurableApplicationContext) applicationContext).close();
     }
 
-    @GetMapping(value = "/debugReadSimType/{simNum}")
+    @GetMapping(value = "/debugReadSimType/{seatId}")
     @ApiOperation("debug读取模拟器类型序列号")
-    public AjaxResult debugReadSimType(@PathVariable("simNum") final String simNum) {
-        return success(commSendService.debugReadSimType(simNum));
+    public AjaxResult debugReadSimType(@PathVariable("seatId") final Long seatId) {
+        return success(commSendService.debugReadSimType(seatId));
     }
 
-    @GetMapping(value = "/debugReadOneFaultResistance/{simNum}/{bindHardwareMsg}")
+    @GetMapping(value = "/debugReadOneFaultResistance/{seatId}/{bindHardwareMsg}")
     @ApiOperation("debug读取一个故障位置数据")
-    public AjaxResult debugReadOneFaultResistance(@PathVariable("simNum") final String simNum,
+    public AjaxResult debugReadOneFaultResistance(@PathVariable("seatId") final Long seatId,
                                                   @PathVariable("bindHardwareMsg") final String bindHardwareMsg) {
-        return success(commSendService.debugReadOneFaultResistance(simNum, bindHardwareMsg));
+        return success(commSendService.debugReadOneFaultResistance(seatId, bindHardwareMsg));
     }
 
-    @GetMapping(value = "/debugReadAllFaultResistance/{simNum}")
+    @GetMapping(value = "/debugReadAllFaultResistance/{seatId}")
     @ApiOperation("debug读取全部故障位置数据")
-    public AjaxResult debugReadAllFaultResistance(@PathVariable("simNum") final String simNum) {
-        return success(commSendService.debugReadAllFaultResistance(simNum));
+    public AjaxResult debugReadAllFaultResistance(@PathVariable("seatId") final Long seatId) {
+        return success(commSendService.debugReadAllFaultResistance(seatId));
     }
 
-    @GetMapping(value = "/debugReadAllFaultResistanceBySimNum/{simNum}")
+    @GetMapping(value = "/debugReadAllFaultResistanceBySimNum/{seatId}")
     @ApiOperation("debug通过simNum读取一台模拟器所有故障答题值,保存[debug_fault]表中,类似交卷")
-    public AjaxResult debugReadAllFaultResistanceBySimNum(@PathVariable("simNum") final String simNum) {
-        return commSendService.debugReadAllFaultResistanceBySimNum(simNum);
+    public AjaxResult debugReadAllFaultResistanceBySimNum(@PathVariable("seatId") final Long seatId) {
+        return commSendService.debugReadAllFaultResistanceBySimNum(seatId);
     }
 
-    @GetMapping(value = "/debugClearOneFault/{simNum}/{bindHardwareMsg}")
+    @GetMapping(value = "/debugClearOneFault/{seatId}/{bindHardwareMsg}")
     @ApiOperation("debug清除一个故障")
-    public AjaxResult debugClearOneFault(@PathVariable("simNum") final String simNum,
+    public AjaxResult debugClearOneFault(@PathVariable("seatId") final Long seatId,
                                          @PathVariable("bindHardwareMsg") final String bindHardwareMsg) {
-        return success(commSendService.debugClearOneFault(simNum, bindHardwareMsg));
+        return success(commSendService.debugClearOneFault(seatId, bindHardwareMsg));
     }
 
-    @GetMapping(value = "/debugClearAllFaultBySimNum/{simNum}")
+    @GetMapping(value = "/debugClearAllFaultBySimNum/{seatId}")
     @ApiOperation("debug通过simNum清除一台模拟器所有故障")
-    public AjaxResult debugClearAllFaultBySimNum(@PathVariable("simNum") final String simNum) {
-        return commSendService.debugClearAllFaultBySimNum(simNum);
+    public AjaxResult debugClearAllFaultBySimNum(@PathVariable("seatId") final Long seatId) {
+        return commSendService.debugClearAllFaultBySeatId(seatId);
     }
 
     @GetMapping(value = "/debugClearAllOnlineSimAllFault/")
@@ -74,26 +79,26 @@ public class HardwareCommDebugController extends BaseController {
 
     @GetMapping(value = "/debugWriteOneFault/{simNum}/{bindHardwareMsg}")
     @ApiOperation("debug下发一个故障")
-    public AjaxResult debugWriteOneFault(@PathVariable("simNum") final String simNum,
+    public AjaxResult debugWriteOneFault(@PathVariable("seatId") final Long seatId,
                                          @PathVariable("bindHardwareMsg") final String bindHardwareMsg) {
-        return success(commSendService.debugWriteOneFault(simNum, bindHardwareMsg));
+        return success(commSendService.debugWriteOneFault(seatId, bindHardwareMsg));
     }
 
-    @GetMapping(value = "/debugWriteAllFault/{simNum}")
+    @GetMapping(value = "/debugWriteAllFault/{seatId}")
     @ApiOperation("debug下发所有故障")
-    public AjaxResult debugWriteAllFault(@PathVariable("simNum") final String simNum) {
-        return success(commSendService.debugWriteAllFault(simNum));
+    public AjaxResult debugWriteAllFault(@PathVariable("seatId") final Long seatId) {
+        return success(commSendService.debugWriteAllFault(seatId));
     }
 
     @GetMapping(value = "/debugWriteSelectedFaultBySimNum/{simNum}/{faultIds}")
     @ApiOperation("debug下发所选故障,保存[debug_fault]表中,类似开始考试")
-    public AjaxResult debugWriteSelectedFaultBySimNum(@PathVariable("simNum") final String simNum,
+    public AjaxResult debugWriteSelectedFaultBySimNum(@PathVariable("seatId") final Long seatId,
                                                       @PathVariable("faultIds") final String[] faultIds,
                                                       @RequestParam final Boolean checkReplace) {
-        return commSendService.debugWriteSelectedFaultBySimNum(simNum, faultIds, checkReplace);
+        return commSendService.debugWriteSelectedFaultBySimNum(seatId, faultIds, checkReplace);
     }
-
     @GetMapping(value = "/buildMsg/")
+
     @ApiOperation("buildMsg")
     public AjaxResult buildSendMsg(@RequestParam final String simNum,
                                    @RequestParam final String orn,
@@ -102,4 +107,14 @@ public class HardwareCommDebugController extends BaseController {
                                    @RequestParam final String data) {
         return commBuildService.buildSendMsgAR(simNum, orn, cmd, cmdId, data);
     }
+
+    @ApiOperation("buildMsgAndSend")
+    public AjaxResult buildMsgAndSend(@RequestParam final String simNum,
+                                   @RequestParam final String orn,
+                                   @RequestParam final String cmd,
+                                   @RequestParam final String cmdId,
+                                   @RequestParam final String data) {
+        // todo:
+        return commBuildService.buildSendMsgAR(simNum, orn, cmd, cmdId, data);
+    }
 }

+ 8 - 13
ruoyi-sim/src/main/java/com/ruoyi/sim/controller/RealExamController.java

@@ -3,6 +3,7 @@ package com.ruoyi.sim.controller;
 import java.util.List;
 
 import com.ruoyi.common.utils.SecurityUtils;
+import com.ruoyi.sim.domain.RealExamCollection;
 import com.ruoyi.sim.domain.vo.RealExamVo;
 import com.ruoyi.sim.service.impl.RealExamService;
 import io.swagger.annotations.Api;
@@ -10,13 +11,7 @@ import io.swagger.annotations.ApiOperation;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.PutMapping;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
 import com.ruoyi.common.annotation.Log;
 import com.ruoyi.common.core.controller.BaseController;
 import com.ruoyi.common.core.domain.AjaxResult;
@@ -76,23 +71,23 @@ public class RealExamController extends BaseController {
 
     @GetMapping("/student/exam/start/{examId}")
     @ApiOperation("[学生][正式使用]开始考试")
-    public AjaxResult studentStartRealExam(@PathVariable("examId") Long examId) {
+    public AjaxResult studentStartRealExam(@PathVariable("examId") Long examId, @RequestParam final String ip) {
         l.info("[学生][正式使用]开始考试");
-        return realExamService.studentStartRealExam(examId);
+        return realExamService.studentStartRealExam(examId, ip, RealExamCollection.Type.EXAM);
     }
 
     @GetMapping("/student/exercise/start/{examId}")
     @ApiOperation("[学生][正式使用]开始练习")
-    public AjaxResult studentStartRealExercise(@PathVariable("examId") Long examId) {
+    public AjaxResult studentStartRealExercise(@PathVariable("examId") Long examId, @RequestParam final String ip) {
         l.info("[学生][正式使用]开始练习");
-        return realExamService.studentStartRealExam(examId);
+        return realExamService.studentStartRealExam(examId, ip, RealExamCollection.Type.EXERCISE);
     }
 
     @GetMapping("/student/self-exercise/start/{examId}")
     @ApiOperation("[学生][正式使用]开始自主练习")
-    public AjaxResult studentStartRealSelfExercise(@PathVariable("examId") Long examId) {
+    public AjaxResult studentStartRealSelfExercise(@PathVariable("examId") Long examId, @RequestParam final String ip) {
         l.info("[学生][正式使用]开始自主练习");
-        return realExamService.studentStartRealExam(examId);
+        return realExamService.studentStartRealExam(examId, ip, RealExamCollection.Type.SELF_EXERCISE);
     }
 
     @GetMapping("/student/exam/answering/{examId}")

+ 18 - 10
ruoyi-sim/src/main/java/com/ruoyi/sim/controller/SeatController.java

@@ -53,7 +53,7 @@ public class SeatController extends BaseController {
      */
     // @PreAuthorize("@ss.hasPermi('sim:seat:list')")
     @GetMapping("/listAll")
-    @ApiOperation("[老师][轮询]查询全部座列表")
+    // @ApiOperation("[老师][轮询]查询全部座列表")
     public TableDataInfo listAll() {
         Seat seat = new Seat();
         startPage();
@@ -64,8 +64,8 @@ public class SeatController extends BaseController {
     /**
      * 导出座列表
      */
-    @PreAuthorize("@ss.hasPermi('sim:seat:export')")
-    @Log(title = "座", businessType = BusinessType.EXPORT)
+    // @PreAuthorize("@ss.hasPermi('sim:seat:export')")
+    // @Log(title = "座", businessType = BusinessType.EXPORT)
     @PostMapping("/export")
     public void export(HttpServletResponse response, Seat seat) {
         List<Seat> list = seatService.selectSeatList(seat);
@@ -76,7 +76,7 @@ public class SeatController extends BaseController {
     /**
      * 获取座详细信息
      */
-    @PreAuthorize("@ss.hasPermi('sim:seat:query')")
+    // @PreAuthorize("@ss.hasPermi('sim:seat:query')")
     @GetMapping(value = "/{seatId}")
     public AjaxResult getInfo(@PathVariable("seatId") Long seatId) {
         return success(seatService.selectSeatBySeatId(seatId));
@@ -85,8 +85,8 @@ public class SeatController extends BaseController {
     /**
      * 新增座
      */
-    @PreAuthorize("@ss.hasPermi('sim:seat:add')")
-    @Log(title = "座", businessType = BusinessType.INSERT)
+    // @PreAuthorize("@ss.hasPermi('sim:seat:add')")
+    // @Log(title = "座", businessType = BusinessType.INSERT)
     @PostMapping
     public AjaxResult add(@RequestBody Seat seat) {
         return toAjax(seatService.insertSeat(seat));
@@ -95,8 +95,8 @@ public class SeatController extends BaseController {
     /**
      * 修改座
      */
-    @PreAuthorize("@ss.hasPermi('sim:seat:edit')")
-    @Log(title = "座", businessType = BusinessType.UPDATE)
+    // @PreAuthorize("@ss.hasPermi('sim:seat:edit')")
+    // @Log(title = "座", businessType = BusinessType.UPDATE)
     @PutMapping
     public AjaxResult edit(@RequestBody Seat seat) {
         return toAjax(seatService.updateSeat(seat));
@@ -105,10 +105,18 @@ public class SeatController extends BaseController {
     /**
      * 删除座
      */
-    @PreAuthorize("@ss.hasPermi('sim:seat:remove')")
-    @Log(title = "座", businessType = BusinessType.DELETE)
+    // @PreAuthorize("@ss.hasPermi('sim:seat:remove')")
+    // @Log(title = "座", businessType = BusinessType.DELETE)
     @DeleteMapping("/{seatIds}")
     public AjaxResult remove(@PathVariable Long[] seatIds) {
         return toAjax(seatService.deleteSeatBySeatIds(seatIds));
     }
+
+    // -------------------------------- tom add  --------------------------------
+
+    @GetMapping("/listAllEnable")
+    @ApiOperation("获取所有没有被禁用的座列表")
+    public AjaxResult listAllEnable() {
+        return seatService.listAllEnableAj();
+    }
 }

+ 36 - 37
ruoyi-sim/src/main/java/com/ruoyi/sim/controller/TestIotController.java

@@ -4,13 +4,13 @@ import com.ruoyi.common.core.controller.BaseController;
 import com.ruoyi.common.core.domain.AjaxResult;
 import com.ruoyi.sim.domain.Fault;
 import com.ruoyi.sim.domain.RealExam;
-import com.ruoyi.sim.service.impl.CommSendService;
-import com.ruoyi.sim.service.impl.FaultService;
-import com.ruoyi.sim.service.impl.MajorService;
-import com.ruoyi.sim.service.impl.RealExamService;
+import com.ruoyi.sim.domain.Seat;
+import com.ruoyi.sim.domain.vo.SimSocketVo;
+import com.ruoyi.sim.service.impl.*;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Lazy;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PathVariable;
 import org.springframework.web.bind.annotation.RequestMapping;
@@ -24,49 +24,35 @@ import java.util.List;
 public class TestIotController extends BaseController {
 
     @Autowired
-    private MajorService majorService;
-    @Autowired
+    @Lazy
     private CommSendService commSendService;
     @Autowired
+    @Lazy
     private RealExamService realExamService;
     @Autowired
+    @Lazy
     private FaultService faultService;
+    @Autowired
+    @Lazy
+    private SocketService socketService;
+    @Autowired
+    @Lazy
+    private CommCheckService commCheckService;
+    @Autowired
+    @Lazy
+    private SeatService seatService;
 
-    @ApiOperation("testIot通信")
+    @ApiOperation("debug666")
     @GetMapping(value = "/{codeId}")
     public AjaxResult testIndex(@PathVariable("codeId") Integer codeId) {
-
         long examId = 2L;
         switch (codeId) {
-            case 1: {
-                commSendService.checkAllSimState();
-            }
-            break;
-            case 2: {
-                // commSnedService.clearOneFault(null, null, null);
-            }
-            break;
-            case 3: {
-                // commSnedService.readOneFaultResistance(null, null);
-            }
-            break;
-            case 4: {
-                // commSnedService.writeOneFault(null, null, null);
-            }
-            break;
-            case 5: {
-//                for (int i = 0; i < 100; i++) {
-//                    commSnedService.readOneFaultResistance(null, null);
-//                }
-            }
-            break;
             case 6: {
                 RealExam re = realExamService.selectRealExamByExamId(1L);
                 commSendService.clearOneSimAllFaultByExam(re);
             }
             break;
             case 10: {
-                commSendService.checkAllSimState();
                 commSendService.clearAll();
                 RealExam re = realExamService.selectRealExamByExamId(1L);
                 commSendService.clearOneSimAllFaultByExam(re);
@@ -82,7 +68,6 @@ public class TestIotController extends BaseController {
                 //
             }
             break;
-
             case 20: {
                 realExamService.studentEnterRealExam(examId);
                 //
@@ -94,7 +79,7 @@ public class TestIotController extends BaseController {
             }
             break;
             case 22: {
-                realExamService.studentStartRealExam(examId);
+                realExamService.studentStartRealExam(examId, null, null);
                 //
             }
             break;
@@ -112,12 +97,26 @@ public class TestIotController extends BaseController {
                 realExamService.studentLoopPostRealExam(examId);
                 //
             }
-            case 66: {
-
+            case 26: {
+                AjaxResult aj = socketService.tryOpenAll();
+                return aj;
+            }
+            case 27: {
+                socketService.closeOne(new SimSocketVo("192.168.1.202", 11001));
+                socketService.closeOne(new SimSocketVo("192.168.1.202", 11008));
+            }
+            case 100: {
+                return socketService.tryOpenAll();
+            }
+            case 101: {
+                Seat seat = seatService.selectSeatBySeatId(10L);
+                return commCheckService.checkOneSeatState(seat, true);
+            }
+            case 102: {
+                return commCheckService.checkAllSeatAndSimState();
             }
-            break;
         }
-        return AjaxResult.success();
+        return AjaxResult.success("ZZZZZZZZZZZZZZZZZZZZ");
     }
 
     @ApiOperation("testIot通信")

+ 17 - 13
ruoyi-sim/src/main/java/com/ruoyi/sim/domain/RealExam.java

@@ -30,6 +30,12 @@ public class RealExam extends BaseEntity {
     private Long examCollectionId;
 
     /**
+     * 模拟器类型
+     */
+    @Excel(name = "模拟器类型")
+    private String simType;
+
+    /**
      * 学员ID/用户ID
      */
     @Excel(name = "学员ID/用户ID")
@@ -48,20 +54,9 @@ public class RealExam extends BaseEntity {
     private Long simId;
 
     /**
-     * 考试状态:
-     * [0]-未登录,
-     * [1]-已登录,
-     * [2]-模拟器检查并下发故障中,
-     * [3]:模拟器检查OK可开考,
-     * [4]-答题中,
-     * [5]-已交卷,
-     * [6]-计算成绩中,
-     * [7]-获取到成绩报告,
-     * [80]-教师标记缺考,
-     * [81]-登录未开始答题,
-     * [90]-模拟器异常结束
+     * 考试状态:[0]-未登录,[1]-已登录,[2]-模拟器检查并下发故障中,[3]:模拟器检查OK可开考,[4]-答题中,[5]-已交卷,[6]-计算成绩中,[7]-获取到成绩报告,[80]-教师标记缺考,[81]-登录未开始答题,[90]-模拟器异常结束
      */
-    @Excel(name = "考试状态")
+    @Excel(name = "考试状态:[0]-未登录,[1]-已登录,[2]-模拟器检查并下发故障中,[3]:模拟器检查OK可开考,[4]-答题中,[5]-已交卷,[6]-计算成绩中,[7]-获取到成绩报告,[80]-教师标记缺考,[81]-登录未开始答题,[90]-模拟器异常结束")
     private String examStatus;
 
     /**
@@ -120,6 +115,14 @@ public class RealExam extends BaseEntity {
         return examCollectionId;
     }
 
+    public void setSimType(String simType) {
+        this.simType = simType;
+    }
+
+    public String getSimType() {
+        return simType;
+    }
+
     public void setUserId(Long userId) {
         this.userId = userId;
     }
@@ -205,6 +208,7 @@ public class RealExam extends BaseEntity {
         return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
                 .append("examId", getExamId())
                 .append("examCollectionId", getExamCollectionId())
+                .append("simType", getSimType())
                 .append("userId", getUserId())
                 .append("seatId", getSeatId())
                 .append("simId", getSimId())

+ 46 - 15
ruoyi-sim/src/main/java/com/ruoyi/sim/domain/Seat.java

@@ -1,5 +1,6 @@
 package com.ruoyi.sim.domain;
 
+import com.ruoyi.sim.domain.vo.SimSocketVo;
 import org.apache.commons.lang3.builder.ToStringBuilder;
 import org.apache.commons.lang3.builder.ToStringStyle;
 import com.ruoyi.common.annotation.Excel;
@@ -24,36 +25,42 @@ public class Seat extends BaseEntity {
      * 座号
      */
     @Excel(name = "座号")
-    private Long seatNum;
+    private Integer seatNum;
 
     /**
-     * 学员端座次上[电脑]绑定的[IP地址]
+     * [电脑]绑定的[IP地址]
      */
-    // @Excel(name = "学员端座次上[电脑]绑定的[IP地址]")
+    @Excel(name = "[电脑]绑定的[IP地址]")
     private String seatBindIp;
 
     /**
-     * 学员端座次上[RS485]绑定的[IP地址]
+     * [RS485]绑定的[IP地址]
      */
-    // @Excel(name = "学员端座次上[RS485]绑定的[IP地址]")
+    @Excel(name = "[RS485]绑定的[IP地址]")
     private String seatRs485Ip;
 
     /**
-     * 学员端座次上[RS485]绑定的[端口]
+     * [RS485]绑定的[端口]
      */
-    // @Excel(name = "学员端座次上[RS485]绑定的[端口]")
-    private Long seatRs485Port;
+    @Excel(name = "[RS485]绑定的[端口]")
+    private Integer seatRs485Port;
+
+    /**
+     * Socket状态:[0]:初始化,[1]:打开,[2]:关闭,[5]:禁用
+     */
+    @Excel(name = "Socket状态:[0]:初始化,[1]:打开,[2]:关闭,[5]:禁用")
+    private String seatRs485SocketState;
 
     /**
      * 当前座上学员/用户ID
      */
-    // @Excel(name = "当前座上学员/用户ID")
+    @Excel(name = "当前座上学员/用户ID")
     private Long currentUserId;
 
     /**
-     * 学员端座次上模拟器的ID:[0]没有连接任何模拟器
+     * 模拟器的ID:[0]没有连接任何模拟器,[xx]:具体某台模拟器
      */
-    // @Excel(name = "学员端座次上模拟器的ID:[0]没有连接任何模拟器")
+    @Excel(name = "模拟器的ID:[0]没有连接任何模拟器,[xx]:具体某台模拟器")
     private Long currentSimId;
 
     public void setSeatId(Long seatId) {
@@ -64,11 +71,11 @@ public class Seat extends BaseEntity {
         return seatId;
     }
 
-    public void setSeatNum(Long seatNum) {
+    public void setSeatNum(Integer seatNum) {
         this.seatNum = seatNum;
     }
 
-    public Long getSeatNum() {
+    public Integer getSeatNum() {
         return seatNum;
     }
 
@@ -88,14 +95,22 @@ public class Seat extends BaseEntity {
         return seatRs485Ip;
     }
 
-    public void setSeatRs485Port(Long seatRs485Port) {
+    public void setSeatRs485Port(Integer seatRs485Port) {
         this.seatRs485Port = seatRs485Port;
     }
 
-    public Long getSeatRs485Port() {
+    public Integer getSeatRs485Port() {
         return seatRs485Port;
     }
 
+    public void setSeatRs485SocketState(String seatRs485SocketState) {
+        this.seatRs485SocketState = seatRs485SocketState;
+    }
+
+    public String getSeatRs485SocketState() {
+        return seatRs485SocketState;
+    }
+
     public void setCurrentUserId(Long currentUserId) {
         this.currentUserId = currentUserId;
     }
@@ -120,6 +135,7 @@ public class Seat extends BaseEntity {
                 .append("seatBindIp", getSeatBindIp())
                 .append("seatRs485Ip", getSeatRs485Ip())
                 .append("seatRs485Port", getSeatRs485Port())
+                .append("seatRs485SocketState", getSeatRs485SocketState())
                 .append("currentUserId", getCurrentUserId())
                 .append("currentSimId", getCurrentSimId())
                 .append("createBy", getCreateBy())
@@ -129,4 +145,19 @@ public class Seat extends BaseEntity {
                 .append("remark", getRemark())
                 .toString();
     }
+
+    // -------------------------------- tom add  --------------------------------
+
+    public static final Long ID_0 = 0L;
+
+    public interface SocketState {
+        String ENABLE_INIT = "0";
+        String ONLINE = "1";
+        String OFFLINE = "2";
+        String DISABLE = "5";
+    }
+
+    public SimSocketVo toSimSocketVo() {
+        return new SimSocketVo(this.getSeatRs485Ip(), this.getSeatRs485Port());
+    }
 }

+ 4 - 0
ruoyi-sim/src/main/java/com/ruoyi/sim/domain/Sim.java

@@ -151,6 +151,8 @@ public class Sim extends BaseEntity {
     }
 
     // -------------------------------- tom add  --------------------------------
+
+    public static final Long ID_0 = 0L;
     /**
      * FZD04B
      */
@@ -180,7 +182,9 @@ public class Sim extends BaseEntity {
         String ENABLE_INIT = "0";
         String ONLINE = "1";
         String OFFLINE = "2";
+        @Deprecated
         String GATEWAY_OFFLINE = "3";
+        @Deprecated
         String SIM_ERROR = "4";
         String DISABLE = "5";
     }

+ 40 - 27
ruoyi-sim/src/main/java/com/ruoyi/sim/domain/SimMsg.java

@@ -3,7 +3,6 @@ package com.ruoyi.sim.domain;
 import java.util.Date;
 
 import com.fasterxml.jackson.annotation.JsonFormat;
-import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.builder.ToStringBuilder;
 import org.apache.commons.lang3.builder.ToStringStyle;
 import com.ruoyi.common.annotation.Excel;
@@ -82,6 +81,8 @@ public class SimMsg extends BaseEntity {
     @Excel(name = "接收报文")
     private String receiveMsg;
 
+    private String receiveOriginalMsg;
+
     /**
      * 接收时间
      */
@@ -96,9 +97,9 @@ public class SimMsg extends BaseEntity {
     private Integer retryCount = 0;
 
     /**
-     * default value false.
+     * default.
      */
-    private Boolean ok = false;
+    private Integer result = Result.DEFAULT_VALUE;
 
     /**
      * default ""
@@ -185,6 +186,14 @@ public class SimMsg extends BaseEntity {
         return receiveMsg;
     }
 
+    public String getReceiveOriginalMsg() {
+        return receiveOriginalMsg;
+    }
+
+    public void setReceiveOriginalMsg(String receiveOriginalMsg) {
+        this.receiveOriginalMsg = receiveOriginalMsg;
+    }
+
     public void setReceiveTime(Date receiveTime) {
         this.receiveTime = receiveTime;
     }
@@ -201,12 +210,12 @@ public class SimMsg extends BaseEntity {
         return retryCount;
     }
 
-    public Boolean getOk() {
-        return ok;
+    public Integer getResult() {
+        return result;
     }
 
-    public void setOk(Boolean ok) {
-        this.ok = ok;
+    public void setResult(Integer result) {
+        this.result = result;
     }
 
     public String getErrorMsg() {
@@ -220,28 +229,32 @@ public class SimMsg extends BaseEntity {
     @Override
     public String toString() {
         return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
-                .append("simMsgId", getSimMsgId())
-                .append("simId", getSimId())
-                .append("examFaultRefId", getExamFaultRefId())
-                .append("examCollectionType", getExamCollectionType())
-                .append("sendMsgState", getSendMsgState())
-                .append("priority", getPriority())
-                .append("sendMsgType", getSendMsgType())
-                .append("sendMsg", getSendMsg())
-                .append("sendTime", getSendTime())
-                .append("receiveMsg", getReceiveMsg())
-                .append("receiveTime", getReceiveTime())
-                .append("retryCount", getRetryCount())
-                .append("ok", getOk())
-                .append("errorMsg", getErrorMsg())
-                .append("createBy", getCreateBy())
-                .append("createTime", getCreateTime())
-                .append("updateBy", getUpdateBy())
-                .append("updateTime", getUpdateTime())
-                .append("remark", getRemark())
+                .append("simMsgId", simMsgId)
+                .append("simId", simId)
+                .append("examFaultRefId", examFaultRefId)
+                .append("examCollectionType", examCollectionType)
+                .append("sendMsgState", sendMsgState)
+                .append("priority", priority)
+                .append("sendMsgType", sendMsgType)
+                .append("sendMsg", sendMsg)
+                .append("sendTime", sendTime)
+                .append("receiveMsg", receiveMsg)
+                .append("receiveOriginalMsg", receiveOriginalMsg)
+                .append("receiveTime", receiveTime)
+                .append("retryCount", retryCount)
+                .append("result", result)
+                .append("errorMsg", errorMsg)
                 .toString();
     }
-    // -------------------------------- tom add  --------------------------------
+
+// -------------------------------- tom add  --------------------------------
+
+    public interface Result {
+        Integer DEFAULT_VALUE = 0;
+        Integer SUCCESS = 200;
+        Integer RECEIVE_CHECK_FAIL = 500;
+        Integer SOCKET_EXCEPTION = 501;
+    }
 
     public SimMsg() {
     }

+ 40 - 3
ruoyi-sim/src/main/java/com/ruoyi/sim/domain/vo/SimSocketVo.java

@@ -1,5 +1,11 @@
 package com.ruoyi.sim.domain.vo;
 
+import com.ruoyi.sim.constant.CommConst;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+
 public class SimSocketVo {
 
     /**
@@ -11,9 +17,6 @@ public class SimSocketVo {
      */
     private Integer port;
 
-    public SimSocketVo() {
-    }
-
     public SimSocketVo(String ip, Integer port) {
         this.ip = ip;
         this.port = port;
@@ -34,4 +37,38 @@ public class SimSocketVo {
     public void setPort(Integer port) {
         this.port = port;
     }
+
+    @Override
+    public String toString() {
+        return new ToStringBuilder(this)
+                .append("ip", ip)
+                .append("port", port)
+                .toString();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+
+        if (o == null || getClass() != o.getClass()) return false;
+
+        SimSocketVo that = (SimSocketVo) o;
+
+        return new EqualsBuilder().append(ip, that.ip).append(port, that.port).isEquals();
+    }
+
+    @Override
+    public int hashCode() {
+        return new HashCodeBuilder(17, 37).append(ip).append(port).toHashCode();
+    }
+
+    public String toKey() {
+        if (StringUtils.isBlank(ip)) {
+            throw new IllegalArgumentException("ip error");
+        }
+        if (port == null || port <= CommConst.PORT_MIN || port >= CommConst.PORT_MAX) {
+            throw new IllegalArgumentException("port error");
+        }
+        return ip + ":" + port;
+    }
 }

+ 8 - 8
ruoyi-sim/src/main/java/com/ruoyi/sim/domain/vo/SocketWrapValue.java

@@ -18,13 +18,13 @@ public class SocketWrapValue {
 
     private Socket socket;
 
-    private Long connectedTimeMillis;
+    private Long okTimeMillis;
 
-    public SocketWrapValue(String ip, Integer port, Socket socket, Long connectedTimeMillis) {
+    public SocketWrapValue(String ip, Integer port, Socket socket, Long okTimeMillis) {
         this.ip = ip;
         this.port = port;
         this.socket = socket;
-        this.connectedTimeMillis = connectedTimeMillis;
+        this.okTimeMillis = okTimeMillis;
     }
 
     public String getIp() {
@@ -43,12 +43,12 @@ public class SocketWrapValue {
         this.socket = socket;
     }
 
-    public Long getConnectedTimeMillis() {
-        return connectedTimeMillis;
+    public Long getOkTimeMillis() {
+        return okTimeMillis;
     }
 
-    public void setConnectedTimeMillis(Long connectedTimeMillis) {
-        this.connectedTimeMillis = connectedTimeMillis;
+    public void setOkTimeMillis(Long okTimeMillis) {
+        this.okTimeMillis = okTimeMillis;
     }
 
     @Override
@@ -56,7 +56,7 @@ public class SocketWrapValue {
         return new ToStringBuilder(this)
                 .append("ip", ip)
                 .append("socket", socket)
-                .append("connectedTimeMillis", connectedTimeMillis)
+                .append("okTimeMillis", okTimeMillis)
                 .toString();
     }
 }

+ 23 - 9
ruoyi-sim/src/main/java/com/ruoyi/sim/service/impl/CommBuildService.java

@@ -27,7 +27,7 @@ public class CommBuildService {
     private SnowflakeIdService idService;
 
     /**
-     * 设备类型读取
+     * 读取设备序列号
      *
      * @param simNum sim.sim_num
      */
@@ -36,6 +36,15 @@ public class CommBuildService {
     }
 
     /**
+     * 询问设备类型和序列号
+     *
+     * @return
+     */
+    public SimMsg buildSendMsgWhichSim() {
+        return buildSendMsg(CommConst.BLANK_SIM_NUM, CMD_READ_TYPE, CMD_ID_GET_SN);
+    }
+
+    /**
      * 故障下发
      *
      * @param simNum          sim.sim_num
@@ -46,7 +55,7 @@ public class CommBuildService {
     }
 
     /**
-     * 状态读取
+     * 读取故障
      *
      * @param simNum          sim.sim_num
      * @param bindHardwareMsg fault.bind_hardware_msg
@@ -56,7 +65,7 @@ public class CommBuildService {
     }
 
     /**
-     * 故障清清除
+     * 清除故障
      *
      * @param simNum          sim.sim_num
      * @param bindHardwareMsg fault.bind_hardware_msg
@@ -65,11 +74,20 @@ public class CommBuildService {
         return buildSendMsg(simNum, CMD_CLEAR_FAULT, bindHardwareMsg);
     }
 
+    /**
+     * 填充内容为空。
+     *
+     * @param simNum
+     * @param cmd
+     * @param cmdId
+     * @return
+     */
     public SimMsg buildSendMsg(final String simNum, final String cmd, final String cmdId) {
-        return buildSendMsg(simNum, ORN_SEND, cmd, cmdId, CMD_DATA_PLACE_HOLDER);
+        return buildSendMsg(simNum, ORN_SEND, cmd, cmdId, CommConst.CMD_DATA_PLACE_HOLDER);
     }
 
     /**
+     * 生成发送指令基本方法。
      *
      * @param simNum
      * @param orn
@@ -123,11 +141,7 @@ public class CommBuildService {
         return smS;
     }
 
-    public AjaxResult buildSendMsgAR(final String simNum,
-                                     final String orn,
-                                     final String cmd,
-                                     final String cmdId,
-                                     final String data) {
+    public AjaxResult buildSendMsgAR(final String simNum, final String orn, final String cmd, final String cmdId, final String data) {
         final SimMsg sm = buildSendMsg(simNum, orn, cmd, cmdId, data);
         return AjaxResult.success(sm);
     }

+ 301 - 0
ruoyi-sim/src/main/java/com/ruoyi/sim/service/impl/CommCheckService.java

@@ -0,0 +1,301 @@
+package com.ruoyi.sim.service.impl;
+
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.sim.constant.CommConst;
+import com.ruoyi.sim.domain.Seat;
+import com.ruoyi.sim.domain.Sim;
+import com.ruoyi.sim.domain.SimMsg;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.List;
+import java.util.Objects;
+
+import static com.ruoyi.sim.constant.CommConst.RETRY_COUNT_0;
+import static com.ruoyi.sim.constant.CommConst.RETRY_COUNT_QUERY_SN_IMPORTANT;
+
+@Service
+public class CommCheckService {
+
+    private static final Logger l = LoggerFactory.getLogger(CommCheckService.class);
+
+    @Autowired
+    private SeatService seatService;
+    @Autowired
+    private SimService simService;
+    @Autowired
+    private CommBuildService commBuildService;
+    @Autowired
+    private CommSendService commSendService;
+    @Autowired
+    private FailedCountService failedCountService;
+    @Autowired
+    private SocketService socketService;
+
+    /**
+     * 等同于ping命令。
+     *
+     * @param ipV4
+     * @return
+     * @throws IOException
+     */
+    public boolean pingIsReachable(String ipV4) {
+        InetAddress ia = null;
+        try {
+            ia = InetAddress.getByName(ipV4);
+            return ia.isReachable(CommConst.PING_TIME_OUT);
+        } catch (UnknownHostException e) {
+            e.printStackTrace();
+            return false;
+        } catch (IOException e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * ping路由器得到状态
+     *
+     * @param routerIp
+     * @return
+     */
+    public AjaxResult checkRouterState(final String routerIp) {
+        if (routerIp == null || StringUtils.isBlank(routerIp)) {
+            throw new IllegalArgumentException("routerIp isBlank");
+        }
+        if (pingIsReachable(routerIp)) {
+            return AjaxResult.success();
+        } else {
+            simService.updateAllEnableState(Sim.State.OFFLINE);
+            seatService.updateAllEnableState(Seat.SocketState.OFFLINE);
+            return AjaxResult.error("路由器[" + routerIp + "]无法连接!");
+        }
+    }
+
+    /**
+     * pingRS485得到状态
+     *
+     * @param rs485Ip
+     * @return
+     */
+    public AjaxResult checkPingRs485State(final String rs485Ip) {
+        if (rs485Ip == null || StringUtils.isBlank(rs485Ip)) {
+            throw new IllegalArgumentException("rs485Ip isBlank");
+        }
+        if (pingIsReachable(rs485Ip)) {
+            return AjaxResult.success();
+        } else {
+            // 更新SocketState
+            seatService.updateSocketStateByRs485Ip(rs485Ip, Seat.SocketState.OFFLINE);
+            return AjaxResult.error("RS485物联网网关[" + rs485Ip + "]无法连接!");
+        }
+    }
+
+    public AjaxResult checkPingStudentPcState(final String studentIp) {
+        if (studentIp == null || StringUtils.isBlank(studentIp)) {
+            throw new IllegalArgumentException("studentIp isBlank");
+        }
+        if (pingIsReachable(studentIp)) {
+            return AjaxResult.success();
+        } else {
+            return AjaxResult.error("学员操作端[" + studentIp + "]无法连接!");
+        }
+    }
+
+    /**
+     * 检查一个座次状况。
+     * 维护 seat表-socket_state字段;
+     * 维护 seat表-sim_id字段;
+     * 维护 sim表-Online、Offline状态;
+     *
+     * @param seat      座次
+     * @param important true:重要的场景 开始考试 重试次数不同,也会进行序列号检查。false:不重要场景 定时巡查。
+     * @return
+     */
+    @Transactional
+    public AjaxResult checkOneSeatState(final Seat seat, final boolean important) {
+        // check args.
+        if (seat == null) {
+            throw new IllegalArgumentException("seat is null");
+        }
+        //
+        socketService.tryOpenAll();
+        //
+        int retryTotalCount;
+        if (important) {
+            retryTotalCount = CommConst.RETRY_COUNT_WHICH_SIM_IMPORTANT;
+        } else {
+            retryTotalCount = CommConst.RETRY_COUNT_0;
+        }
+        SimMsg smS01 = commBuildService.buildSendMsgWhichSim();
+        SimMsg smR01 = commSendService.send(smS01, seat, null, retryTotalCount, CommConst.SLEEP_SHORT);
+        Integer result = smR01.getResult();
+        if (Objects.equals(result, SimMsg.Result.SUCCESS)) {
+            final String simNum = CommParseUtils.subSimNum(smR01);
+            Sim sim = simService.uniqueBySimNum(simNum);
+            if (sim == null) {
+                return AjaxResult.error("找不到模拟器[" + simNum + "]对应数据。");
+            } else {
+                l.info("在座次[{}]上发现模拟器[{}]", seat.getSeatNum(), sim.getSimNum());
+            }
+            // 更新SimId
+            seatService.updateSimIdBySeatNum(seat.getSeatNum(), sim.getSimId());
+            // 更新Sim状态
+            simService.updateSimStateBySimId(sim.getSimId(), Sim.State.ONLINE);
+            return AjaxResult.success("成功,检查一个座次[" + seat.getSeatNum() + "]OK!模拟器[" + sim.getSimNum() + "]在线。");
+        } else if (Objects.equals(result, SimMsg.Result.RECEIVE_CHECK_FAIL)) {
+            return AjaxResult.error("失败,报文回复异常。");
+        } else if (Objects.equals(result, SimMsg.Result.SOCKET_EXCEPTION)) {
+            // 更新SimId
+            seatService.updateSimIdBySeatNum(seat.getSeatNum(), Sim.ID_0);
+            return AjaxResult.success("成功,检查一个座次[" + seat.getSeatNum() + "]OK!未连接模拟器。");
+        }
+        return AjaxResult.error("失败");
+    }
+
+    /**
+     * 纯数据库查询,模拟器是否在线。
+     * 模拟器是否被禁用。
+     *
+     * @param simId
+     * @return
+     */
+    public AjaxResult checkOneSimOnlineState(final Long simId) {
+        Sim sim = simService.selectSimBySimId(simId);
+        if (sim != null) {
+            switch (sim.getSimState()) {
+                case Sim.State.ONLINE: {
+                    return AjaxResult.success("模拟器[" + sim.getSimNum() + "]在线!");
+                }
+                case Sim.State.OFFLINE: {
+                    return AjaxResult.success("模拟器[" + sim.getSimNum() + "]离线!");
+                }
+                case Sim.State.DISABLE: {
+                    return AjaxResult.error("模拟器[" + sim.getSimNum() + "]禁用!");
+                }
+            }
+        }
+        return AjaxResult.error("模拟器[" + Objects.requireNonNull(sim).getSimNum() + "]XXXX!");
+    }
+
+    /**
+     * 默认Seat中已经有CurrentSimId数据
+     * 检查回应报文模拟器类型是否正确。
+     *
+     * @param seat          座次
+     * @param important
+     * @param targetSimType 期望模拟器目标类型
+     * @return
+     */
+    public AjaxResult checkOneSimType(final Seat seat, final boolean important, final String targetSimType) {
+        // check args.
+        if (seat == null) {
+            throw new IllegalArgumentException("seat is null");
+        }
+        if (seat.getCurrentSimId() == null || Sim.ID_0.equals(seat.getCurrentSimId())) {
+            throw new IllegalArgumentException("sim id is 0");
+        }
+        if (!simService.existBySimId(seat.getCurrentSimId())) {
+            return AjaxResult.error("模拟器ID[" + seat.getCurrentSimId() + "]不存在!");
+        }
+        //
+        final String msgError = "连接模拟器类型或序列号不正确!应该连接型号:";
+        final String msgOk = "连接模拟器类型或序列号正确!";
+        int retryTotalCount;
+        if (important) {
+            retryTotalCount = RETRY_COUNT_QUERY_SN_IMPORTANT;
+        } else {
+            retryTotalCount = RETRY_COUNT_0;
+        }
+        Sim sim = simService.selectSimBySimId(seat.getCurrentSimId());
+        SimMsg smS = commBuildService.buildSendMsgReadSimType(sim.getSimNum());
+        SimMsg smR = commSendService.send(smS, seat, sim, retryTotalCount, CommConst.SLEEP_SHORT);
+        if (StringUtils.isNotBlank(smR.getReceiveMsg())) {
+            final String content = CommParseUtils.subContentData(smR);
+            switch (targetSimType) {
+                case Sim.TYPE_0001 -> {
+                    if (content.startsWith(CommConst.TYPE_0001_SN_PREFIX) && content.endsWith(sim.getSimNum())) {
+                        return AjaxResult.success(msgOk);
+                    } else {
+                        return AjaxResult.error(msgError + Sim.TYPE_NAME_MAP.get(targetSimType));
+                    }
+                }
+                case Sim.TYPE_0002 -> {
+                    if (content.startsWith(CommConst.TYPE_0002_SN_PREFIX) && content.endsWith(sim.getSimNum())) {
+                        return AjaxResult.success(msgOk);
+                    } else {
+                        return AjaxResult.error(msgError + Sim.TYPE_NAME_MAP.get(targetSimType));
+                    }
+                }
+                case Sim.TYPE_0003 -> {
+                    if (content.startsWith(CommConst.TYPE_0003_SN_PREFIX) && content.endsWith(sim.getSimNum())) {
+                        return AjaxResult.success(msgOk);
+                    } else {
+                        return AjaxResult.error(msgError + Sim.TYPE_NAME_MAP.get(targetSimType));
+                    }
+                }
+                default -> throw new IllegalStateException("Unexpected value: " + targetSimType);
+            }
+        }
+        return AjaxResult.error("失败,检查一个模拟器[" + sim.getSimNum() + "]型号或序列号执行错误!");
+    }
+
+    /**
+     * 默认Seat中已经有CurrentSimId数据
+     *
+     * @param seat
+     * @param important
+     * @return
+     */
+    @Transactional
+    public AjaxResult checkOneSimOnlineState(final Seat seat, final boolean important) {
+        // check args.
+        if (seat == null) {
+            throw new IllegalArgumentException("seat is null");
+        }
+        if (seat.getCurrentSimId() == null || Sim.ID_0.equals(seat.getCurrentSimId())) {
+            return AjaxResult.error("模拟器ID不存在!");
+        }
+        if (!simService.existBySimId(seat.getCurrentSimId())) {
+            return AjaxResult.error("模拟器ID[" + seat.getCurrentSimId() + "]不存在!");
+        }
+        //
+        int retryTotalCount;
+        if (important) {
+            retryTotalCount = RETRY_COUNT_QUERY_SN_IMPORTANT;
+        } else {
+            retryTotalCount = RETRY_COUNT_0;
+        }
+        Sim sim = simService.selectSimBySimId(seat.getCurrentSimId());
+        SimMsg smS02 = commBuildService.buildSendMsgReadSimType(sim.getSimNum());
+        SimMsg smR02 = commSendService.send(smS02, seat, sim, retryTotalCount, CommConst.SLEEP_SHORT);
+        if (StringUtils.isNotBlank(smR02.getReceiveMsg())) {
+            // 只要返回正确报文,即认为在线。
+            simService.updateSimStateBySimId(sim.getSimId(), Sim.State.ONLINE);
+            // SocketOldService实现。
+            // socketOldService.commFailCountClearOne(sim.getSimId());
+            failedCountService.reset0(seat.toSimSocketVo());
+            return AjaxResult.success("成功,检查一个模拟器[" + sim.getSimNum() + "]OK!");
+        }
+        return AjaxResult.error("失败,检查一个模拟器[" + sim.getSimNum() + "]在线状态执行错误!");
+    }
+
+    /**
+     * [定时执行]查找所有没有被手动禁用的座次 和 座次上的模拟器。
+     */
+    public AjaxResult checkAllSeatAndSimState() {
+        List<Seat> list = seatService.listAllEnable();
+        list.forEach(seat -> {
+            checkOneSeatState(seat, false);
+            // checkOneSimState(seat, false);
+        });
+        return AjaxResult.success("检查完毕,成功!");
+    }
+}

+ 28 - 0
ruoyi-sim/src/main/java/com/ruoyi/sim/service/impl/CommParseUtils.java

@@ -0,0 +1,28 @@
+package com.ruoyi.sim.service.impl;
+
+
+import com.ruoyi.sim.domain.SimMsg;
+import org.apache.commons.lang3.StringUtils;
+
+public class CommParseUtils {
+
+    /**
+     * 截取 内容报文。
+     *
+     * @param
+     * @return "01 02 03 04"
+     */
+    public static String subContentData(SimMsg sm) {
+        if (StringUtils.isBlank(sm.getReceiveMsg())) {
+            throw new IllegalArgumentException("sm isBlank");
+        }
+        return StringUtils.substring(sm.getReceiveMsg(), 10, 18);
+    }
+
+    public static String subSimNum(SimMsg sm) {
+        if (StringUtils.isBlank(sm.getReceiveMsg())) {
+            throw new IllegalArgumentException("sm isBlank");
+        }
+        return StringUtils.substring(sm.getReceiveMsg(), 2, 4);
+    }
+}

+ 27 - 88
ruoyi-sim/src/main/java/com/ruoyi/sim/service/impl/CommReceiveService.java

@@ -32,62 +32,6 @@ public class CommReceiveService {
     @Autowired
     private FaultService faultService;
 
-    /**
-     * 只要返回信息,即认为在线。
-     *
-     * @param sm
-     * @param s
-     * @return
-     */
-    public AjaxResult checkOneSimState(SimMsg sm, Sim s) {
-        if (s == null) {
-            l.warn("s is null");
-            return AjaxResult.error("sim is null");
-        }
-        if (StringUtils.isNotBlank(sm.getReceiveMsg())) {
-            simService.updateSimStateBySimId(s.getSimId(), Sim.State.ONLINE);
-        }
-        return AjaxResult.success();
-    }
-
-    /**
-     * c.endsWith(s.getSimNum())
-     * 检查回应报文模拟器类型是否正确。
-     *
-     * @param sm
-     * @param s
-     * @return
-     */
-    public AjaxResult checkOneSimSn(SimMsg sm, Sim s) {
-        final String simType = s.getSimType();
-        final String c = subContentData(sm.getReceiveMsg());
-        final String msgError = "连接模拟器类型不正确!";
-        switch (simType) {
-            case Sim.TYPE_0001 -> {
-                if (c.startsWith(CommConst.TYPE_0001_SN_PREFIX)) {
-                    return AjaxResult.success();
-                } else {
-                    return AjaxResult.error(msgError);
-                }
-            }
-            case Sim.TYPE_0002 -> {
-                if (c.startsWith(CommConst.TYPE_0002_SN_PREFIX)) {
-                    return AjaxResult.success();
-                } else {
-                    return AjaxResult.error(msgError);
-                }
-            }
-            case Sim.TYPE_0003 -> {
-                if (c.startsWith(CommConst.TYPE_0003_SN_PREFIX)) {
-                    return AjaxResult.success();
-                } else {
-                    return AjaxResult.error(msgError);
-                }
-            }
-            default -> throw new IllegalStateException("Unexpected value: " + simType);
-        }
-    }
-
     public void clearOneFault(SimMsg sm, Sim s, RealExamFault reF, Fault f) {
         // check
 
@@ -111,7 +55,7 @@ public class CommReceiveService {
         // check
 
         //
-        String faultQuestionValue = subContentData(sm.getReceiveMsg());
+        String faultQuestionValue = CommParseUtils.subContentData(sm);
         // todo:
         if (StringUtils.isBlank(faultQuestionValue)) {
             l.warn("faultQuestionValue is empty!");
@@ -178,7 +122,7 @@ public class CommReceiveService {
     public void setFaultAnswerValue(SimMsg sm, Sim s, RealExamFault reF, Fault f, String refState) {
         // check
         //
-        String faultAnswerValue = subContentData(sm.getReceiveMsg());
+        String faultAnswerValue = CommParseUtils.subContentData(sm);
         // todo:
         if (StringUtils.isBlank(faultAnswerValue)) {
             l.warn("faultAnswerValue is empty!");
@@ -206,18 +150,6 @@ public class CommReceiveService {
         }
     }
 
-    /**
-     * 截取 内容报文。
-     *
-     * @param receiveMsg
-     * @return
-     */
-    public String subContentData(String receiveMsg) {
-        if (StringUtils.isEmpty(receiveMsg)) {
-            return "";
-        }
-        return StringUtils.substring(receiveMsg, 10, 18);
-    }
 
     /**
      * 开始考试 检查 故障部位 检查
@@ -228,7 +160,7 @@ public class CommReceiveService {
      * @return
      */
     public AjaxResult getOneFaultCheck(SimMsg sm, Sim s, Fault f) {
-        String checkValue = subContentData(sm.getReceiveMsg());
+        String checkValue = CommParseUtils.subContentData(sm);
         if (s == null) {
             return AjaxResult.error("没有对应模拟器!");
         }
@@ -256,43 +188,50 @@ public class CommReceiveService {
     }
 
     /**
-     * check receiveMsg
+     * 处理报文前端加00的情况,最多可能加5组00
      *
      * @param receiveMsg
      * @return
      */
-    public AjaxResult checkReceiveMsg(String receiveMsg) {
+    public String removeRrefix0(String receiveMsg) {
+        int count = 0;
+        while (StringUtils.startsWith(receiveMsg, CommConst.PREFIX_ERROR_0)) {
+            receiveMsg = StringUtils.removeStartIgnoreCase(receiveMsg, CommConst.PREFIX_ERROR_0);
+            count = count + 1;
+        }
+        l.info("####remove count#### = [{}]", count);
+        return receiveMsg;
+    }
+
+    /**
+     * 有返回报文的情况下,检查Receive的报文。
+     *
+     * @param receiveMsg
+     * @return
+     */
+    public AjaxResult checkReceiveMsg(final String receiveMsg) {
         l.info("####checkReceiveMsg#### = [{}]", receiveMsg);
-        String start = "ReceiveMsg ";
+        String msgErr = "ReceiveMsg ";
         // check:不能是empty
         if (StringUtils.isBlank(receiveMsg)) {
-            return AjaxResult.error(start + "isBlank");
-        }
-        // check:处理报文前端加00的情况,最多可能加5组00
-        {
-            int count = 0;
-            while (StringUtils.startsWith(receiveMsg, CommConst.PREFIX_ERROR_0)) {
-                receiveMsg = StringUtils.removeStartIgnoreCase(receiveMsg, CommConst.PREFIX_ERROR_0);
-                count = count + 1;
-            }
-            l.info("####remove count#### = [{}]", count);
+            return AjaxResult.error(msgErr + "isBlank");
         }
         // check:长度
         if (receiveMsg.length() != LENGTH_24) {
-            return AjaxResult.error(start + "length error.length not 24.");
+            return AjaxResult.error(msgErr + "length error.length not 24.");
         }
         // check:数据方向
         final String orn = StringUtils.substring(receiveMsg, 4, 6);
         if (!ORN_RECEIVE.equals(orn)) {
-            return AjaxResult.error(start + "orn error.");
+            return AjaxResult.error(msgErr + "orn error.");
         }
         // check:前缀
         if (!StringUtils.startsWith(receiveMsg, PREFIX)) {
-            return AjaxResult.error(start + "not start with AA.");
+            return AjaxResult.error(msgErr + "not start with AA.");
         }
         // check:后缀
         if (!StringUtils.endsWith(receiveMsg, SUFFIX)) {
-            return AjaxResult.error(start + "not end with 55.");
+            return AjaxResult.error(msgErr + "not end with 55.");
         }
         // 计算CRC16
         // todo:

+ 277 - 291
ruoyi-sim/src/main/java/com/ruoyi/sim/service/impl/CommSendService.java

@@ -3,21 +3,21 @@ package com.ruoyi.sim.service.impl;
 import com.ruoyi.common.core.domain.AjaxResult;
 import com.ruoyi.common.utils.DateUtils;
 import com.ruoyi.sim.config.SimConfig;
-import com.ruoyi.sim.config.SimDebugConfig;
+import com.ruoyi.sim.constant.CommConst;
 import com.ruoyi.sim.domain.*;
+import com.ruoyi.sim.domain.vo.SimSocketVo;
 import org.apache.commons.lang3.StringUtils;
 import org.slf4j.LoggerFactory;
 import org.slf4j.Logger;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
-import java.net.InetAddress;
 import java.net.Socket;
-import java.net.UnknownHostException;
 import java.util.*;
 
 import static com.ruoyi.sim.constant.CommConst.*;
@@ -35,6 +35,8 @@ public class CommSendService {
     @Autowired
     private CommReceiveService simReceiveService;
     @Autowired
+    private SeatService seatService;
+    @Autowired
     private SimService simService;
     @Autowired
     private FaultService faultService;
@@ -48,8 +50,13 @@ public class CommSendService {
     private CommBuildService commBuildService;
     @Autowired
     private DebugFaultService debugFaultService;
+    // private SocketOldService socketOldService;
+    @Autowired
+    private SocketService socketService;
+    @Autowired
+    private FailedCountService failedCountService;
     @Autowired
-    private SocketOldService socketOldService;
+    private CommCheckService commCheckService;
     @Autowired
     CommReceiveService commReceiveService;
     @Autowired
@@ -71,13 +78,12 @@ public class CommSendService {
             }
             List<RealExamFault> listRef = realExamFaultService.listAllType2State2and3ByExamId(e.getExamId());
             listRef.forEach(ref -> {
-                Sim s = simService.selectSimBySimId(e.getSimId());
+                RealExam re = realExamService.selectRealExamByExamId(ref.getExamId());
+                Seat seat = seatService.selectSeatBySeatId(re.getSeatId());
+                Sim sim = simService.selectSimBySimId(e.getSimId());
                 Fault f = faultService.selectFaultByFaultId(ref.getFaultId());
-                if (f != null &&
-                        Fault.Type.REAL_GZBW.equals(f.getFaultType()) &&
-                        Fault.State.ENABLE.equals(f.getFaultState())
-                ) {
-                    readOneSimOneFaultResistance(s, ref, f, null);
+                if (f != null && Fault.Type.REAL_GZBW.equals(f.getFaultType()) && Fault.State.ENABLE.equals(f.getFaultState())) {
+                    readOneSimOneFaultResistance(seat, sim, ref, f, null);
                 }
             });
         });
@@ -95,9 +101,10 @@ public class CommSendService {
         l.info("readOneExamAtLast");
         List<RealExamFault> list = realExamFaultService.listAllType2State2and3ByExamId(re.getExamId());
         for (RealExamFault ref : list) {
-            Sim s = simService.selectSimBySimId(re.getSimId());
+            Seat seat = seatService.selectSeatBySeatId(re.getSeatId());
+            Sim sim = simService.selectSimBySimId(re.getSimId());
             Fault f = faultService.selectFaultByFaultId(ref.getFaultId());
-            readOneSimOneFaultResistance(s, ref, f, RealExamFault.State.FINISH);
+            readOneSimOneFaultResistance(seat, sim, ref, f, RealExamFault.State.FINISH);
         }
         // 计算扣分。
         // 最后都读取到,才算扣分。
@@ -107,36 +114,34 @@ public class CommSendService {
         }
     }
 
-    public void readOneSimAtLastByDebug(Sim s) {
+    public void readOneSimAtLastByDebug(Seat seat, Sim sim) {
         l.info("readOneSimAtLastByDebug");
-        List<Fault> list = faultService.listType3(s.getSimType());
+        List<Fault> list = faultService.listType3(sim.getSimType());
         for (Fault f : list) {
-            readOneSimOneFaultResistance(s, null, f, null);
+            readOneSimOneFaultResistance(seat, sim, null, f, null);
         }
     }
 
-    public AjaxResult debugReadAllFaultResistanceBySimNum(String simNum) {
+    public AjaxResult debugReadAllFaultResistanceBySimNum(final Long seatId) {
         // check
 
         //
         // 打开socket
         {
-            AjaxResult ar1 = socketOldService.openSocket();
-            if (ar1.isError()) {
-                return ar1;
-            }
+            socketService.tryOpenAll();
         }
         //
-        Sim s = simService.uniqueBySimNum(simNum);
+        Seat seat = seatService.selectSeatBySeatId(seatId);
+        Sim sim = gggSimBySeatId(seatId);
         {
-            AjaxResult arE3 = checkOneSimStateActive(s);
+            AjaxResult arE3 = checkOneSimStateActive(seat);
             if (arE3.isError()) {
                 return arE3;
             }
         }
-        s = simService.uniqueBySimNum(simNum);
+        sim = simService.uniqueBySimNum(sim.getSimNum());
         // todo: aj改造
-        readOneSimAtLastByDebug(s);
+        readOneSimAtLastByDebug(seat, sim);
         return AjaxResult.success("成功读取!");
     }
 
@@ -168,7 +173,7 @@ public class CommSendService {
      */
     public void scheduledReadSim() {
         l.info("scheduledReadSim");
-        if (!realExamCollectionService.existOpened()) {
+        if (!realExamCollectionService.existAtLeastOneOpened()) {
             l.info("没有open的考试集合");
             return;
         }
@@ -197,15 +202,6 @@ public class CommSendService {
         // debugReadSimType(simNum);
         // l.info("bRea:" + bRea);
 
-        {
-            Socket s = socketOldService.getCachedSocket();
-            l.info("cachedSocket.isConnected():" + s.isConnected());
-            l.info("cachedSocket.isBound():" + s.isBound());
-            l.info("cachedSocket.isClosed():" + s.isClosed());
-            l.info("cachedSocket.isInputShutdown():" + s.isInputShutdown());
-            l.info("cachedSocket.isOutputShutdown():" + s.isOutputShutdown());
-        }
-
 //            debugReadAllFaultResistance(simNum, simType);
 
 //            debugWriteAllFault(simNum, simType);
@@ -223,121 +219,78 @@ public class CommSendService {
 
     /**
      * 连接情况 的 定时任务。
-     * 执行频率: 3min
+     * 执行频率: 5min
      */
     public void scheduledConnect() {
         l.info("scheduled####Connect  连接情况 的 定时任务");
-        if (!SimDebugConfig.SCHEDULED_CONNECT) {
+//        if (!SimDebugConfig.SCHEDULED_CONNECT) {
+        if (true) {
             l.info("连接情况 的 定时任务被禁用!");
             return;
         }
-        if (!realExamCollectionService.existOpened()) {
-            l.info("没有open的考试集合");
-            return;
-        }
-        if (!isReachable(config.getRouterIp())) {
-            l.warn("ping RouterIp not ok");
-            simService.updateAllEnableState(Sim.State.GATEWAY_OFFLINE);
-            return;
+
+        // 暂时注释
+//        if (!realExamCollectionService.existAtLeastOneOpened()) {
+//            l.info("没有open的任何集合");
+//            return;
+//        }
+
+        //
+        {
+            AjaxResult ar01 = commCheckService.checkRouterState(config.getRouterIp());
+            if (ar01.isError()) {
+                return;
+            }
         }
-        if (!isReachable(config.getRs485Ip())) {
-            l.warn("ping Rs485Ip not ok");
-            simService.updateAllEnableState(Sim.State.GATEWAY_OFFLINE);
-            return;
+        //
+        {
+            seatService.listAllRs485Ip().forEach(rs485Ip -> {
+                commCheckService.checkPingRs485State(rs485Ip);
+            });
         }
+        // SocketOldService实现
+        // socketOldService.openSocket();
 
-        socketOldService.openSocket();
-        if (socketOldService.isCachedSocketOk()) {
-            checkAllSimState();
-        }
+        // SocketOldService实现
+//        if (socketOldService.isCachedSocketOk()) {
+//            checkAllSeatAndSimState();
+//        }
+
+        socketService.tryOpenAll();
+        commCheckService.checkAllSeatAndSimState();
     }
 
     /**
      * 主动更新模拟器状态。
+     * <p>
+     * <p>
+     * 主动查询一次模拟器状态。更新模拟器在线/离线状态;否则返回对应错误。
+     * todo:需要重新考虑
      *
-     * @param s
+     * @param seat
      * @return
      */
-    public AjaxResult checkOneSimStateActive(Sim s) {
-        final long simId = s.getSimId();
+    @Deprecated
+    public AjaxResult checkOneSimStateActive(Seat seat) {
         // 这句可能会调整模拟器状态,后面需要重新查询。
-        {
-            AjaxResult ar1 = checkOneSimState(s, true);
-            if (ar1.isError()) {
-                return ar1;
-            }
+        commCheckService.checkOneSeatState(seat, true);
+        AjaxResult ar1 = commCheckService.checkOneSimOnlineState(seat, true);
+        if (ar1.isError()) {
+            return ar1;
         }
         // 重新最新模拟器。
-        s = simService.selectSimBySimId(simId);
+        Sim sim = gggSimBySeatId(seat.getSeatId());
         // 如果模拟器离线
-        if (s != null && Sim.State.ONLINE.equals(s.getSimState())) {
+        if (sim != null && Sim.State.ONLINE.equals(sim.getSimState())) {
             return AjaxResult.success();
         } else {
             return AjaxResult.error("未连接模拟器,请检查连接!");
         }
     }
 
-    /**
-     * @param s
-     * @param important true 重试次数不同,也会进行序列号检查。
-     * @return
-     */
-    public AjaxResult checkOneSimState(final Sim s, final boolean important) {
-        // check
-        if (s == null) {
-            return AjaxResult.error("sim is null");
-        }
-        if (Sim.State.DISABLE.equals(s.getSimState())) {
-            l.warn("sim DISABLE,模拟器被禁用,sim = {}", s);
-            return AjaxResult.error("模拟器被禁用");
-        }
-        if (StringUtils.isBlank(s.getSimType()) || StringUtils.isBlank(s.getSimNum())) {
-            l.warn("sim error data {}", s);
-            return AjaxResult.error("模拟器数据错误。");
-        }
-        //
-        SimMsg smS = commBuildService.buildSendMsgReadSimType(s.getSimNum());
-        int retryCount = RETRY_COUNT_0;
-        long sleep = SLEEP_SHORT;
-        if (important) {
-            retryCount = RETRY_COUNT_QUERY_SN_IMPORTANT;
-        }
-        SimMsg smR = send(smS, s, retryCount, sleep);
-        if (StringUtils.isNotBlank(smR.getReceiveMsg())) {
-            l.info("isNotBlank");
-            socketOldService.commFailCountClearOne(s.getSimId());
-        }
-        simReceiveService.checkOneSimState(smR, s);
-        {
-            if (important) {
-                AjaxResult ar1 = simReceiveService.checkOneSimSn(smR, s);
-                if (ar1.isError()) {
-                    return ar1;
-                }
-            }
-        }
-        return AjaxResult.success();
-    }
-
-    /**
-     * 查找所有没有被手动禁用,并order by sim_num的模拟器列表。检查所有模拟器状态。
-     */
-    public void checkAllSimState() {
-        // RealExamCollection ecF = realExamCollectionService.selectRealExamCollectionOpened();
-        // l.info("ecF.getSimType() = {}", ecF.getSimType());
-        // if (ecF != null) {
-
-        // }
-        List<Sim> list = simService.listAllEnable(); // ecF.getSimType()
-        l.info("checkAllSimState list.size() = {}", list.size());
-        list.forEach(s -> {
-            checkOneSimState(s, false);
-        });
-    }
-
     @Async("tp-comm")
     public void checkAllSimStateAsync() {
-        checkAllSimState();
+        commCheckService.checkAllSeatAndSimState();
     }
 
     /**
@@ -359,27 +312,26 @@ public class CommSendService {
             l.info("清除exam list = {}", list.size());
         }
         assert list != null;
-        list
-                .forEach(ref -> {
-                    Fault f = faultService.selectFaultByFaultId(ref.getFaultId());
-                    if (faultService.isDisable(f.getFaultId())) {
-                        l.warn("故障 {} -被禁用", f.getName());
-                        throw new IllegalArgumentException("故障被禁用");
-                    }
-                    Sim s = simService.selectSimBySimId(re.getSimId());
-                    // check
+        Seat seat = seatService.selectSeatBySeatId(re.getSeatId());
+        list.forEach(ref -> {
+            Fault f = faultService.selectFaultByFaultId(ref.getFaultId());
+            if (faultService.isDisable(f.getFaultId())) {
+                l.warn("故障 {} -被禁用", f.getName());
+                throw new IllegalArgumentException("故障被禁用");
+            }
+            Sim sim = simService.selectSimBySimId(re.getSimId());
+            // check
 
-                    //
-                    clearOneSimOneFault(s, ref, f);
-                });
+            //
+            clearOneSimOneFault(seat, sim, ref, f);
+        });
     }
 
-    public void clearOneSimAllFaultBySim(Sim s) {
-        l.info("clearOneSimAllFaultBySim = {}", s);
-        faultService.listType3EnableBySimType(s.getSimType())
-                .forEach(f -> {
-                    clearOneSimOneFault(s, null, f);
-                });
+    public void clearOneSimAllFaultBySim(Seat seat, Sim sim) {
+        l.info("clearOneSimAllFaultBySim = {}", sim);
+        faultService.listType3EnableBySimType(sim.getSimType()).forEach(f -> {
+            clearOneSimOneFault(seat, sim, null, f);
+        });
     }
 
     /**
@@ -394,11 +346,13 @@ public class CommSendService {
 
     public void clearAll() {
         // todo:
-        simService.listAllEnable().forEach(s -> {
-            String simType = s.getSimType();
+        // 根据Seat数据遍历
+        seatService.listAllEnable().forEach(seat -> {
+            Sim sim = gggSimBySeatId(seat.getSeatId());
+            String simType = sim.getSimType();
             List<Fault> listF = faultService.listType3EnableBySimType(simType);
             listF.forEach(f -> {
-                clearOneSimOneFault(s, null, f);
+                clearOneSimOneFault(seat, sim, null, f);
             });
         });
     }
@@ -406,82 +360,89 @@ public class CommSendService {
     /**
      * debug读取模拟器类型序列号
      *
-     * @param simNum
+     * @param seatId
      * @return
      */
-    public SimMsg debugReadSimType(final String simNum) {
-        SimMsg sm = commBuildService.buildSendMsgReadSimType(simNum);
-        return send(sm, null, RETRY_COUNT_0, SLEEP_SHORT);
+    public SimMsg debugReadSimType(final Long seatId) {
+        Seat seat = seatService.selectSeatBySeatId(seatId);
+        Sim sim = gggSimBySeatId(seatId);
+        SimMsg sm = commBuildService.buildSendMsgReadSimType(sim.getSimNum());
+        return send(sm, seat, sim, CommConst.RETRY_COUNT_0, CommConst.SLEEP_SHORT);
     }
 
     /**
      * debug清除一个故障
      *
-     * @param simNum
+     * @param seatId
      * @param bindHardwareMsg
      * @return
      */
-    public SimMsg debugClearOneFault(final String simNum, final String bindHardwareMsg) {
-        SimMsg sm = commBuildService.buildSendMsgClearFault(simNum, bindHardwareMsg);
-        Sim s = simService.uniqueBySimNum(simNum);
-        return send(sm, s, RETRY_COUNT_CLEAR_ONE_FAULT, SLEEP_LONG);
+    public SimMsg debugClearOneFault(final Long seatId, final String bindHardwareMsg) {
+        Seat seat = seatService.selectSeatBySeatId(seatId);
+        Sim sim = gggSimBySeatId(seatId);
+        SimMsg sm = commBuildService.buildSendMsgClearFault(sim.getSimNum(), bindHardwareMsg);
+        return send(sm, seat, sim, RETRY_COUNT_CLEAR_ONE_FAULT, SLEEP_LONG);
     }
 
     public AjaxResult debugClearAllOnlineSimAllFault() {
         l.info("debugClearAllOnlineSimAllFault");
         simService.listAllOnline().forEach(s -> {
-            AjaxResult ar = debugClearAllFaultBySimNum(s.getSimNum());
+            // AjaxResult ar = debugClearAllFaultBySeatId(0);// todo:尚未实现
         });
+        debugFaultService.deleteAll();
         return AjaxResult.success("清除成功,清除所有在线模拟器所有故障!");
     }
 
     /**
      * debug清除所有故障
      *
-     * @param simNum
+     * @param seatId
      * @return
      */
-    public AjaxResult debugClearAllFaultBySimNum(final String simNum) {
-        Sim s1 = simService.uniqueBySimNum(simNum);
+    @Transactional
+    public AjaxResult debugClearAllFaultBySeatId(final Long seatId) {
+        Seat seat = seatService.selectSeatBySeatId(seatId);
+        Sim sim = gggSimBySeatId(seatId);
+        // todo:
         {
-            AjaxResult arE3 = checkOneSimStateActive(s1);
+            AjaxResult arE3 = checkOneSimStateActive(seat);
             if (arE3.isError()) {
                 return arE3;
             }
         }
-        s1 = simService.uniqueBySimNum(simNum);
-        if (s1 == null) {
+        sim = simService.uniqueBySimNum(sim.getSimNum());
+        if (sim == null) {
             return AjaxResult.error("清除失败,对应simNum模拟器不存在!");
         }
-        if (!Sim.State.ONLINE.equals(s1.getSimState())) {
+        if (!Sim.State.ONLINE.equals(sim.getSimState())) {
             return AjaxResult.error("清除失败,模拟器尚未在线!");
         }
         List<SimMsg> list = new ArrayList<>();
-        for (String b : getGZBWBySimType(s1.getSimType())) {
-            SimMsg sm = debugClearOneFault(simNum, b);
+        for (String b : getGZBWBySimType(sim.getSimType())) {
+            SimMsg sm = debugClearOneFault(seatId, b);
             list.add(sm);
-            if (sm != null && !sm.getOk()) {
-
-            }
+//            if (sm != null && !sm.isOk()) {
+//
+//            }
         }
+        debugFaultService.deleteAll();
         return AjaxResult.success("清除成功,清除当前模拟器所有故障!");
     }
 
     /**
-     * @param s
+     * @param sim
      * @param reF 可以为空,表示不关联考试的,单独执行的。调试模式下为空。
      * @param f
      */
-    public void clearOneSimOneFault(Sim s, RealExamFault reF, Fault f) {
-        l.info("clearOneSimOneFault 清除One故障:getSimNum = {},getBindHardwareMsg = {},fault.getName = {}",
-                s.getSimNum(), f.getBindHardwareMsg(), f.getName());
+    public void clearOneSimOneFault(Seat seat, Sim sim, RealExamFault reF, Fault f) {
+        l.info("clearOneSimOneFault 清除One故障:getSimNum = {},getBindHardwareMsg = {},fault.getName = {}", sim.getSimNum(), f.getBindHardwareMsg(), f.getName());
         // check todo:
 
         // step1
-        SimMsg smS = commBuildService.buildSendMsgClearFault(s.getSimNum(), f.getBindHardwareMsg());
-        SimMsg smR = send(smS, s, RETRY_COUNT_CLEAR_ONE_FAULT, SLEEP_LONG);
+        SimMsg smS = commBuildService.buildSendMsgClearFault(sim.getSimNum(), f.getBindHardwareMsg());
+        SimMsg smR = send(smS, seat, sim, RETRY_COUNT_CLEAR_ONE_FAULT, SLEEP_LONG);
         if (reF != null) {
-            simReceiveService.clearOneFault(smR, s, reF, f);
+            simReceiveService.clearOneFault(smR, sim, reF, f);
         } else {
             l.info("reF == null");
         }
@@ -504,29 +465,31 @@ public class CommSendService {
     /**
      * debug下发一个故障
      *
-     * @param simNum
+     * @param seatId
      * @param bindHardwareMsg
      * @return
      * @throws IOException
      */
-    public SimMsg debugWriteOneFault(final String simNum, final String bindHardwareMsg) {
-        SimMsg sm = commBuildService.buildSendMsgWriteFault(simNum, bindHardwareMsg);
-        return send(sm, null, RETRY_COUNT_WRITE_ONE_FAULT, SLEEP_LONG);
+    public SimMsg debugWriteOneFault(final Long seatId, final String bindHardwareMsg) {
+        Seat seat = seatService.selectSeatBySeatId(seatId);
+        Sim sim = gggSimBySeatId(seatId);
+        SimMsg sm = commBuildService.buildSendMsgWriteFault(sim.getSimNum(), bindHardwareMsg);
+        return send(sm, seat, sim, RETRY_COUNT_WRITE_ONE_FAULT, SLEEP_LONG);
     }
 
     /**
      * debug下发所有故障
      *
-     * @param simNum
+     * @param seatId
      * @return
      * @throws IOException
      */
-    public List<SimMsg> debugWriteAllFault(final String simNum) {
+    public List<SimMsg> debugWriteAllFault(final Long seatId) {
         List<SimMsg> list = new ArrayList<>();
-        Sim s = simService.uniqueBySimNum(simNum);
-        String simType = s.getSimType();
-        for (String b : getGZBWBySimType(simType)) {
-            list.add(debugWriteOneFault(simNum, b));
+        Sim sim = gggSimBySeatId(seatId);
+        String simType = sim.getSimType();
+        for (String bind : getGZBWBySimType(simType)) {
+            list.add(debugWriteOneFault(seatId, bind));
         }
         return list;
     }
@@ -535,14 +498,13 @@ public class CommSendService {
      * todo:尚未实现
      * 实现方式类似 studentStartRealExam方法。
      *
-     * @param simNum
+     * @param seatId
      * @param faultIds
      * @param checkReplace 是否进行可换件检查
      * @return
      */
-    public AjaxResult debugWriteSelectedFaultBySimNum(final String simNum,
-                                                      final String[] faultIds,
-                                                      final Boolean checkReplace) {
+    @Transactional
+    public AjaxResult debugWriteSelectedFaultBySimNum(final Long seatId, final String[] faultIds, final Boolean checkReplace) {
         //
         l.info("faultIds.length = {}", faultIds.length);
         {
@@ -552,29 +514,26 @@ public class CommSendService {
         // check faultIds 有效性
 
         //
-        Sim s = simService.uniqueBySimNum(simNum);
+        Seat seat = seatService.selectSeatBySeatId(seatId);
+        Sim sim = gggSimBySeatId(seatId);
         // check sim
         // 打开socket
         {
-            AjaxResult ar1 = socketOldService.openSocket();
-            l.info("ar1 = {}", ar1);
-            if (ar1.isError()) {
-                return ar1;
-            }
+            socketService.tryOpenAll();
         }
         // Step 1 主动查询一次模拟器状态。
         {
-            AjaxResult arE3 = checkOneSimStateActive(s);
+            AjaxResult arE3 = checkOneSimStateActive(seat);
             if (arE3.isError()) {
                 return arE3;
             }
         }
-        s = simService.uniqueBySimNum(simNum);
+        sim = simService.uniqueBySimNum(sim.getSimNum());
         // Step 2
         // msg判断,是否含有"故障部位"字符串
         {
             if (checkReplace) {
-                AjaxResult arE2 = readOneSimAllFaultCheck(s);
+                AjaxResult arE2 = readOneSimAllFaultCheck(seat, sim);
                 if (arE2.isError()) {
                     return arE2;
                 }
@@ -582,7 +541,7 @@ public class CommSendService {
         }
         // Step 3 清除对应一台模拟器 所有故障部位故障。
         {
-            clearOneSimAllFaultBySim(s);
+            clearOneSimAllFaultBySim(seat, sim);
         }
         // Step 4 下发对应一台模拟器 出题选中的 故障位置故障。
         {
@@ -590,11 +549,11 @@ public class CommSendService {
             for (int i = 0; i < faultIds.length; i++) {
                 faults[i] = faultService.selectFaultByFaultId(faultIds[i]);
             }
-            writeOneSimAllSelectFaultByDebug(s, faults);
+            writeOneSimAllSelectFaultByDebug(seat, sim, faults);
         }
         // Step 5 读取
         {
-            readOneSimAllFaultFirstTimeBySim(s, faultIds);
+            readOneSimAllFaultFirstTimeBySim(seat, sim, faultIds);
         }
         return AjaxResult.success("下发故障流程执行成功!");
     }
@@ -629,41 +588,42 @@ public class CommSendService {
         realExamService.updateOneState(re.getExamId(), RealExam.State.SIM_WRITING);
         List<RealExamFault> list = realExamFaultService.listAllType2FlagYesClearedStateByExamId(re.getExamId());
         for (RealExamFault ref : list) {
-            Sim s = simService.selectSimBySimId(re.getSimId());
+            Seat seat = seatService.selectSeatBySeatId(re.getSeatId());
+            Sim sim = simService.selectSimBySimId(re.getSimId());
             Fault f = faultService.selectFaultByFaultId(ref.getFaultId());
             // 选中的才下发。
-            writeOneSimOneFault(s, ref, f);
+            writeOneSimOneFault(seat, sim, ref, f);
         }
     }
 
-    public void writeOneSimAllSelectFaultByDebug(Sim s, Fault[] faults) {
+    public void writeOneSimAllSelectFaultByDebug(Seat seat, Sim sim, Fault[] faults) {
         for (Fault f : faults) {
-            writeOneSimOneFault(s, null, f);
+            writeOneSimOneFault(seat, sim, null, f);
         }
     }
 
-    public void writeOneSimOneFault(Sim s, RealExamFault ref, Fault f) {
-        l.info("下发故障:getSimId = {},fault.getName = {}", s.getSimId(), f.getName());
+    public void writeOneSimOneFault(Seat seat, Sim sim, RealExamFault ref, Fault f) {
+        l.info("下发故障:getSimId = {},fault.getName = {}", sim.getSimId(), f.getName());
         // todo:ref is null.
         // 下发故障
-        SimMsg smA1 = commBuildService.buildSendMsgWriteFault(s.getSimNum(), f.getBindHardwareMsg());
-        SimMsg smA2 = send(smA1, s, RETRY_COUNT_WRITE_ONE_FAULT, SLEEP_LONG);
+        SimMsg smA1 = commBuildService.buildSendMsgWriteFault(sim.getSimNum(), f.getBindHardwareMsg());
+        SimMsg smA2 = send(smA1, seat, sim, RETRY_COUNT_WRITE_ONE_FAULT, SLEEP_LONG);
     }
 
     /**
      * 开始考试前检查读取。
      *
-     * @param s
+     * @param sim
      * @return
      */
-    public AjaxResult readOneSimAllFaultCheck(Sim s) {
+    public AjaxResult readOneSimAllFaultCheck(Seat seat, Sim sim) {
         Fault q = new Fault();
         q.setFaultType(Fault.Type.REAL_GZBW);
-        q.setSimType(s.getSimType());
+        q.setSimType(sim.getSimType());
         List<Fault> list = faultService.selectFaultList(q);
         List<Fault> listNG = new ArrayList<>();
         for (Fault f : list) {
-            AjaxResult ar = readOneSimOneFaultCheck(s, f);
+            AjaxResult ar = readOneSimOneFaultCheck(seat, sim, f);
             if (ar.isError()) {
                 listNG.add(f);
                 l.info("故障部位[" + f.getBindHardwareMsg() + "][" + f.getReplaceName() + "]未正确安装;");
@@ -686,15 +646,15 @@ public class CommSendService {
     /**
      * 检查读取。
      *
-     * @param s
+     * @param sim
      * @param f
      * @return
      */
-    public AjaxResult readOneSimOneFaultCheck(Sim s, Fault f) {
-        l.info("readOneSimOneFaultCheck s = {},f = {}", s, f);
-        SimMsg sm1 = commBuildService.buildSendMsgReadFaultResistance(s.getSimNum(), f.getBindHardwareMsg());
-        SimMsg sm2 = send(sm1, s, RETRY_COUNT_CHECK_ONE_FAULT, SLEEP_LONG);
-        return simReceiveService.getOneFaultCheck(sm2, s, f);
+    public AjaxResult readOneSimOneFaultCheck(Seat seat, Sim sim, Fault f) {
+        l.info("readOneSimOneFaultCheck sim = {},f = {}", sim, f);
+        SimMsg sm1 = commBuildService.buildSendMsgReadFaultResistance(sim.getSimNum(), f.getBindHardwareMsg());
+        SimMsg sm2 = send(sm1, seat, sim, RETRY_COUNT_CHECK_ONE_FAULT, SLEEP_LONG);
+        return simReceiveService.getOneFaultCheck(sm2, sim, f);
     }
 
     /**
@@ -706,91 +666,93 @@ public class CommSendService {
         l.info("readOneSimAllFaultFirstTimeByExam re = {}", re);
         List<RealExamFault> list = realExamFaultService.listAllType2(re.getExamId());
         for (RealExamFault ref : list) {
-            Sim s = simService.selectSimBySimId(re.getSimId());
+            Seat seat = seatService.selectSeatBySeatId(re.getSeatId());
+            Sim sim = simService.selectSimBySimId(re.getSimId());
             Fault f = faultService.selectFaultByFaultId(ref.getFaultId());
-            readOneSimOneFaultFirstTime(s, ref, f, null);
+            readOneSimOneFaultFirstTime(seat, sim, ref, f, null);
         }
     }
 
     /**
      * 第一次读取,作为出题值。debug模式。
      *
-     * @param s
+     * @param sim
      * @param faultIds
      */
-    public void readOneSimAllFaultFirstTimeBySim(Sim s, final String[] faultIds) {
-        l.info("readOneSimAllFaultFirstTimeBySim s = {}", s);
-        List<Fault> list = faultService.listType3(s.getSimType());
+    public void readOneSimAllFaultFirstTimeBySim(Seat seat, Sim sim, final String[] faultIds) {
+        l.info("readOneSimAllFaultFirstTimeBySim s = {}", sim);
+        List<Fault> list = faultService.listType3(sim.getSimType());
         for (Fault f : list) {
-            readOneSimOneFaultFirstTime(s, null, f, faultIds);
+            readOneSimOneFaultFirstTime(seat, sim, null, f, faultIds);
         }
     }
 
     /**
      * 第一次读取,作为出题值。
      *
-     * @param s
+     * @param sim
      * @param ref      debug调试模式为空。可以为空。
      * @param f
      * @param faultIds debug调试模式为空。
      */
-    public void readOneSimOneFaultFirstTime(Sim s, RealExamFault ref, Fault f, String[] faultIds) {
+    public void readOneSimOneFaultFirstTime(Seat seat, Sim sim, RealExamFault ref, Fault f, String[] faultIds) {
         l.info("readOneSimOneFaultFirstTime");
         // 读取一次当前电阻代表值作为出题值。
-        SimMsg sm1 = commBuildService.buildSendMsgReadFaultResistance(s.getSimNum(), f.getBindHardwareMsg());
-        SimMsg sm2 = send(sm1, s, RETRY_COUNT_WRITE_ONE_FAULT, SLEEP_LONG);
-        simReceiveService.setFaultQuestionValue(sm2, s, ref, f, faultIds);
+        SimMsg sm1 = commBuildService.buildSendMsgReadFaultResistance(sim.getSimNum(), f.getBindHardwareMsg());
+        SimMsg sm2 = send(sm1, seat, sim, RETRY_COUNT_WRITE_ONE_FAULT, SLEEP_LONG);
+        simReceiveService.setFaultQuestionValue(sm2, sim, ref, f, faultIds);
     }
 
     /**
      * debug读取一个故障位置数据
      *
-     * @param simNum
+     * @param seatId
      * @param bindHardwareMsg
      * @return
-     * @throws IOException
      */
-    public SimMsg debugReadOneFaultResistance(final String simNum, final String bindHardwareMsg) {
-        SimMsg sm = commBuildService.buildSendMsgReadFaultResistance(simNum, bindHardwareMsg);
-        return send(sm, null, RETRY_COUNT_0, SLEEP_SHORT);
+    public SimMsg debugReadOneFaultResistance(final Long seatId, final String bindHardwareMsg) {
+        Seat seat = seatService.selectSeatBySeatId(seatId);
+        Sim sim = gggSimBySeatId(seatId);
+        SimMsg sm = commBuildService.buildSendMsgReadFaultResistance(sim.getSimNum(), bindHardwareMsg);
+        return send(sm, seat, null, RETRY_COUNT_0, SLEEP_SHORT);
     }
 
     /**
      * debug读取全部故障位置数据
      *
-     * @param simNum
+     * @param seatId
      * @return
-     * @throws IOException
      */
-    public List<SimMsg> debugReadAllFaultResistance(final String simNum) {
+    public List<SimMsg> debugReadAllFaultResistance(final Long seatId) {
+        Sim sim = gggSimBySeatId(seatId);
         List<SimMsg> list = new ArrayList<>();
-        String simType = simService.uniqueBySimNum(simNum).getSimType();
+        String simType = simService.uniqueBySimNum(sim.getSimNum()).getSimType();
         for (String b : getGZBWBySimType(simType)) {
-            list.add(debugReadOneFaultResistance(simNum, b));
+            list.add(debugReadOneFaultResistance(seatId, b));
         }
         return list;
     }
 
     /**
-     * @param s
+     * @param sim
      * @param reF
      * @param f
      * @param refState 中间轮询是null,交卷最后一次读取为finish状态。用来修改状态的。debug模式下执行为null。
      */
-    public void readOneSimOneFaultResistance(Sim s, RealExamFault reF, Fault f, String refState) {
+    public void readOneSimOneFaultResistance(Seat seat, Sim sim, RealExamFault reF, Fault f, String refState) {
         l.info("readOneSimOneFaultResistance");
-        SimMsg sm1 = commBuildService.buildSendMsgReadFaultResistance(s.getSimNum(), f.getBindHardwareMsg());
+        SimMsg sm1 = commBuildService.buildSendMsgReadFaultResistance(sim.getSimNum(), f.getBindHardwareMsg());
         SimMsg sm2 = null;
         if (reF != null && refState != null) {
             if (RealExamFault.State.FINISH.equals(refState)) { // 是否最后一次读取。
-                sm2 = send(sm1, s, RETRY_COUNT_READ_ONE_RESISTANCE, SLEEP_SHORT);
+                sm2 = send(sm1, seat, sim, RETRY_COUNT_READ_ONE_RESISTANCE, SLEEP_SHORT);
             } else {
-                sm2 = send(sm1, s, RETRY_COUNT_0, SLEEP_SHORT);
+                sm2 = send(sm1, seat, sim, RETRY_COUNT_0, SLEEP_SHORT);
             }
         } else {
-            sm2 = send(sm1, s, RETRY_COUNT_READ_ONE_RESISTANCE, SLEEP_SHORT);
+            sm2 = send(sm1, seat, sim, RETRY_COUNT_READ_ONE_RESISTANCE, SLEEP_SHORT);
         }
-        simReceiveService.setFaultAnswerValue(sm2, s, reF, f, refState);
+        simReceiveService.setFaultAnswerValue(sm2, sim, reF, f, refState);
     }
 
     private long previousSendSleep = 0;
@@ -799,12 +761,13 @@ public class CommSendService {
      * send hex message
      *
      * @param sm              发送
-     * @param s               可以为空,更新最后发送/接收时间 用。
+     * @param seat            不能为空
+     * @param sim             可以为空!更新最后发送/接收时间 用。
      * @param retryTotalCount 重试次数
      * @param sleep           不使用传入0,不进行挂起。
      * @return
      */
-    public synchronized SimMsg send(final SimMsg sm, final Sim s, final int retryTotalCount, final long sleep) {
+    public synchronized SimMsg send(final SimMsg sm, final Seat seat, final Sim sim, final int retryTotalCount, final long sleep) {
         try {
             if (!config.isCommGlobal()) {
                 l.warn("isCommGlobal == false [模拟器通信被禁用!]");
@@ -813,28 +776,37 @@ public class CommSendService {
             if (sm == null || sm.getSendMsg() == null || StringUtils.isBlank(sm.getSendMsg())) {
                 throw new IllegalArgumentException("SimMsg IllegalArgument");
             }
+            // sim
+            if (seat == null) {
+                throw new IllegalArgumentException("seat is null");
+            }
             if (sleep < 0) {
                 throw new IllegalArgumentException("SimMsg sleep");
             }
             // log.
             {
-                l.info("####SendMsg#### == [{}]", sm);
+                l.info("####发送#### == Seat[{}],SimMsg[{}]", seat, sm);
             }
             // 如果没有打开socket,顺道打开。正好后面要sleep
-            socketOldService.openSocket();
+            // SocketOldService实现
+            // socketOldService.openSocket();
+            socketService.openOne(seat.toSimSocketVo());
             {
-                // sleep ,追求顺序请求。
+                // sleep挂起线程,追求顺序请求。
                 if (sleep > 0 && previousSendSleep != 0L) {
                     Thread.sleep(previousSendSleep);
                 }
             }
             previousSendSleep = sleep;
-            InputStream is = socketOldService.getCachedSocket().getInputStream();
-            OutputStream os = socketOldService.getCachedSocket().getOutputStream();
+            // SocketOldService实现
+            // Socket socket = socketOldService.getCachedSocket();
+            Socket socket = socketService.get(seat);
+            InputStream is = socket.getInputStream();
+            OutputStream os = socket.getOutputStream();
             os.write(hexStrToByteArrs(sm.getSendMsg()));
             sm.setSendTime(DateUtils.getNowDate());
-            if (s != null) {
-                simService.updateLastSentTime(s);
+            if (sim != null) {
+                simService.updateLastSentTime(sim);
             }
             byte[] buffer = new byte[LENGTH_24];
             int length = is.read(buffer);
@@ -842,31 +814,49 @@ public class CommSendService {
             for (int i = 0; i < length; i++) {
                 sbHex.append(String.format("%02X", buffer[i]));
             }
-            sm.setReceiveMsg(sbHex.toString());
+            String receiveWith0 = sbHex.toString();
+            // 原始带0的收到报文。
+            sm.setReceiveOriginalMsg(receiveWith0);
+            sm.setReceiveMsg(commReceiveService.removeRrefix0(receiveWith0));
             sm.setReceiveTime(DateUtils.getNowDate());
             // log.
             {
-                l.info("####ReceiveMsg#### = [{}]", sm);
-            }
-            {
-                AjaxResult ar =    commReceiveService.checkReceiveMsg(sm.getReceiveMsg());
+                AjaxResult ar = commReceiveService.checkReceiveMsg(sm.getReceiveMsg());
                 if (ar.isError()) {
                     // todo:
-                    l.warn("####Fail#### = {}", sm);
+                    l.warn("####接收错误#### = {}", sm);
+                    sm.setResult(SimMsg.Result.RECEIVE_CHECK_FAIL);
                     return sm;
+                } else {
+                    l.info("####接收成功#### = {}", sm);
                 }
             }
-            if (s != null) {
-                simService.updateLastReceivedTime(s);
+            if (sim != null) {
+                simService.updateLastReceivedTime(sim);
             }
-        } catch (InterruptedException | IOException e) {   // SocketTimeoutException
+            sm.setResult(SimMsg.Result.SUCCESS);
+            // 最后返回报文实体。
+            return sm;
+        } catch (InterruptedException | IOException e) {
+            l.error("SocketTimeoutException");// SocketTimeoutException
             e.printStackTrace();
+            sm.setResult(SimMsg.Result.SOCKET_EXCEPTION);
+            if (sim != null) {
+                l.info("fail sim.getSimId() = {}", sim.getSimId());
+            }
+            // SocketOldService实现
+            // boolean limit = socketOldService.commFailCountAdd1(Objects.requireNonNull(sim).getSimId());
+            SimSocketVo ssv = seat.toSimSocketVo();
             // 失败计数
-            l.info("fail sim data = {}", s);
-            boolean limit = socketOldService.commFailCountAdd1(Objects.requireNonNull(s).getSimId());
-            if (limit) {
-                simService.updateSimStateBySimId(s.getSimId(), Sim.State.OFFLINE);
-                socketOldService.commFailCountClearOne(s.getSimId());
+            failedCountService.plus1(ssv);
+            if (failedCountService.isReachedMax(ssv, retryTotalCount)) {
+                // 达到重试次数上限,认为模拟器离线
+                if (sim != null) {
+                    simService.updateSimStateBySimId(sim.getSimId(), Sim.State.OFFLINE);
+                }
+                // SocketOldService实现
+                // socketOldService.commFailCountClearOne(sim.getSimId());
+                failedCountService.reset0(ssv);
             }
             // 先考虑一台模拟器演示。
             // 进行重试 start
@@ -876,14 +866,15 @@ public class CommSendService {
             }
             if (sm.getRetryCount() < retryTotalCount) {
                 sm.retryCountPlus1();
-                send(sm, s, retryTotalCount, sleep);
+                send(sm, seat, sim, retryTotalCount, sleep);
                 l.warn("####RetryTotalCount重试#### = {}", sm);
             } else {
                 l.warn("####RetryTotalCount达到重试上限#### = {}", sm);
             }
             // 进行重试 end
+            // 最后返回报文实体。
+            return sm;
         }
-        return sm;
     }
 
     /**
@@ -899,28 +890,6 @@ public class CommSendService {
         return null;
     }
 
-
-    /**
-     * 等同于ping命令。
-     *
-     * @param ipV4
-     * @return
-     * @throws IOException
-     */
-    public boolean isReachable(String ipV4) {
-        InetAddress ia = null;
-        try {
-            ia = InetAddress.getByName(ipV4);
-            return ia.isReachable(2048);
-        } catch (UnknownHostException e) {
-            e.printStackTrace();
-            return false;
-        } catch (IOException e) {
-            e.printStackTrace();
-            return false;
-        }
-    }
-
     /**
      * https://mvnrepository.com/artifact/com.infiniteautomation/modbus4j/3.0.3
      */
@@ -972,4 +941,21 @@ public class CommSendService {
         }
         return hexString.toString();
     }
+
+    /**
+     * todo:不存在部分数据
+     *
+     * @param seatId
+     * @return
+     */
+    public Sim gggSimBySeatId(Long seatId) {
+        Seat seat = seatService.selectSeatBySeatId(seatId);
+        AjaxResult ar01 = commCheckService.checkOneSeatState(seat, true);
+        if (ar01.isSuccess()) {
+            Long simId = seatService.selectSeatBySeatId(seatId).getCurrentSimId();
+            return simService.selectSimBySimId(simId);
+        } else {
+            throw new IllegalArgumentException("error gggSimBySeatId");
+        }
+    }
 }

+ 0 - 2
ruoyi-sim/src/main/java/com/ruoyi/sim/service/impl/DebugFaultService.java

@@ -103,11 +103,9 @@ public class DebugFaultService {
         return null;
     }
 
-    @Transactional
     public int deleteAll() {
         DebugFault q = new DebugFault();
         List<DebugFault> list = selectDebugFaultList(q);
-        Long[] refIds = new Long[list.size()];
         for (int i = 0; i < list.size(); i++) {
             deleteDebugFaultByRefId(list.get(i).getRefId());
         }

+ 20 - 4
ruoyi-sim/src/main/java/com/ruoyi/sim/service/impl/FailedCountService.java

@@ -1,5 +1,6 @@
 package com.ruoyi.sim.service.impl;
 
+import com.ruoyi.sim.domain.vo.SimSocketVo;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.stereotype.Service;
@@ -17,7 +18,18 @@ public class FailedCountService {
     private static final int INIT_SIZE = 16;
     private static HashMap<String, AtomicInteger> failedMap = new HashMap<>(INIT_SIZE);
 
-    public int plus1(final String key) {
+    /**
+     * @param key
+     * @param limit include limit
+     * @return
+     */
+    public boolean isReachedMax(final SimSocketVo ssv, final int limit) {
+        final String key = ssv.toKey();
+        return (failedMap.containsKey(key) && failedMap.get(key).get() >= limit);
+    }
+
+    public int plus1(final SimSocketVo ssv) {
+        final String key = ssv.toKey();
         if (!failedMap.containsKey(key)) {
             failedMap.put(key, new AtomicInteger(COUNT_ADD_1));
         } else {
@@ -26,7 +38,8 @@ public class FailedCountService {
         return failedMap.get(key).get();
     }
 
-    public int get(final String key) {
+    public int get(final SimSocketVo ssv) {
+        final String key = ssv.toKey();
         if (failedMap.containsKey(key)) {
             return failedMap.get(key).get();
         } else {
@@ -34,8 +47,11 @@ public class FailedCountService {
         }
     }
 
-    public void reset0(final String key) {
-        failedMap.get(key).set(COUNT_0);
+    public void reset0(final SimSocketVo ssv) {
+        final String key = ssv.toKey();
+        if (failedMap.containsKey(key)) {
+            failedMap.get(key).set(COUNT_0);
+        }
     }
 
     public void resetAll() {

+ 12 - 6
ruoyi-sim/src/main/java/com/ruoyi/sim/service/impl/RealExamCollectionService.java

@@ -36,7 +36,7 @@ public class RealExamCollectionService extends Ele6RYBaseService {
     @Lazy
     private CommSendService commSendService;
     @Autowired
-    private SocketOldService socketOldService;
+    private SocketService socketService;
 
     /**
      * 查询考试集合
@@ -321,11 +321,15 @@ public class RealExamCollectionService extends Ele6RYBaseService {
             closeAllExamExcludeId(ref.getExamCollectionId());
         }
         // Step 4:尝试打开所有Socket,提前准备,允许有打开失败的
-
-        AjaxResult ar1 = socketOldService.openSocket();
+        // SocketOldService实现。
+        // AjaxResult ar1 = socketOldService.openSocket();
+        //
+        AjaxResult ar1 = socketService.tryOpenAll();
         if (ar1.isError()) {
             return ar1;
         }
+        //
+
         // 更新相关数据
 
 
@@ -344,11 +348,11 @@ public class RealExamCollectionService extends Ele6RYBaseService {
     }
 
     /**
-     * 是否有一个open的考试集合。
+     * 是否有一个open的任何类型的集合。
      *
      * @return
      */
-    public boolean existOpened() {
+    public boolean existAtLeastOneOpened() {
         return (selectRealExamCollectionOpened() != null);
     }
 
@@ -385,7 +389,9 @@ public class RealExamCollectionService extends Ele6RYBaseService {
     }
 
     public AjaxResult closeAll() {
-        return socketOldService.closeSocket();
+        // SocketOldService实现
+        // return socketOldService.closeSocket();
+        return AjaxResult.success();
     }
 
     private void closeAllType(final String type) {

+ 110 - 23
ruoyi-sim/src/main/java/com/ruoyi/sim/service/impl/RealExamService.java

@@ -13,6 +13,7 @@ import com.ruoyi.sim.domain.vo.RealExamVo;
 import com.ruoyi.sim.domain.vo.StudentRealExamIngVo;
 import com.ruoyi.sim.domain.vo.StudentRealExamPostVo;
 import com.ruoyi.sim.domain.vo.StudentRealExamPreVo;
+import org.apache.commons.lang3.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -113,7 +114,12 @@ public class RealExamService {
     @Lazy
     private CommSendService commSendService;
     @Autowired
+    @Lazy
+    private CommCheckService commCheckService;
+    @Autowired
     private SimConfig simConfig;
+    @Autowired
+    private SocketService socketService;
 
     /**
      * examId 是否有效。
@@ -287,10 +293,12 @@ public class RealExamService {
      * [学生]开始考试
      *
      * @param examId
-     * @return RealExam
+     * @param studentBindIp
+     * @param type
+     * @return
      */
     @Transactional
-    public AjaxResult studentStartRealExam(final Long examId) {
+    public AjaxResult studentStartRealExam(final Long examId, final String studentBindIp, final String type) {
         l.info("studentStartRealExam = {}", examId);
         // todo: 暂时没有解决方案 检查 考试的sim和seat,是否正确对应。
         {
@@ -299,43 +307,120 @@ public class RealExamService {
             // l.info("fake re = {}", re);
         }
         // check id data.
+        // Step :检查参数有效性。
         {
-            AjaxResult arE1 = checkExamId(examId);
-            if (arE1.isError()) {
-                return arE1;
+            AjaxResult ar01 = checkExamId(examId);
+            if (ar01.isError()) {
+                return ar01;
             }
         }
+        if (StringUtils.isBlank(studentBindIp)) {
+            return AjaxResult.error("ip地址无效。");
+        }
         RealExam re = selectRealExamByExamId(examId);
-        Sim s = simService.selectSimBySimId(re.getSimId());
-        // Step 1 主动查询一次模拟器状态。获取模拟器在线/离线状态。
+        Seat seat = seatService.uniqueByBindIp(studentBindIp);
         {
-            AjaxResult arE3 = commSendService.checkOneSimStateActive(s);
-            if (arE3.isError()) {
-                return arE3;
+            if (seat == null) {
+                throw new IllegalArgumentException("XXX");
             }
         }
-        s = simService.selectSimBySimId(re.getSimId());
-        // Step 2 读取对应一台模拟器 所有故障部位值。检查模拟器所有的 真实的 故障部位 是否异常 或者 空值。特殊的故障部位要单独判断。
+        // Step :ping通 路由器。
+        {
+            AjaxResult ar = commCheckService.checkRouterState(simConfig.getRouterIp());
+            if (ar.isError()) {
+                return ar;
+            }
+        }
+        // Step :ping通 学员端电脑。
+        {
+            AjaxResult ar = commCheckService.checkPingStudentPcState(studentBindIp);
+            if (ar.isError()) {
+                return ar;
+            }
+        }
+        // Step :ping通 RS485。
+        {
+            AjaxResult ar = commCheckService.checkPingRs485State(seat.getSeatRs485Ip());
+            if (ar.isError()) {
+                // todo:重复
+                // 更新SimId
+                seatService.updateSimIdBySeatNum(seat.getSeatNum(), Seat.ID_0);
+                // 更新SocketState
+                seatService.updateSocketStateBySeatNum(seat.getSeatNum(), Seat.SocketState.OFFLINE);
+                return ar;
+            } else {
+                // Ping通不代表在线,Socket连接建立表示在线。
+            }
+        }
+        // Step :如果有缓存Socket并且可用,使用缓存Socket,检查并建立Socket连接;否则返回对应错误。
+        {
+            AjaxResult ar = socketService.openOne(seat.toSimSocketVo());
+            if (ar.isError()) {
+                return ar;
+            }
+        }
+        // Step :发送通用询问指令,询问是连接的哪种型号的哪一台模拟器;否则返回对应错误。
+        {
+            AjaxResult ar = commCheckService.checkOneSeatState(seat, true);
+            if (ar.isError()) {
+                return ar;
+            }
+        }
+        // 重新查询。已经确定simId了。
+        {
+            // 修改exam表对应examId的一条数据,填充并锁定seat_id和sim_id值。
+            // 设置上seatId和simId
+            re = selectRealExamByExamId(examId);
+            seat = seatService.uniqueByBindIp(studentBindIp);
+            re.setSeatId(seat.getSeatId());
+            re.setSimId(seat.getCurrentSimId());
+            l.debug("re = {}", re);
+            updateRealExam(re);
+        }
+        // 查询模拟器在线状态,纯DB查询。
+        {
+            AjaxResult ar = commCheckService.checkOneSimOnlineState(seat.getCurrentSimId());
+            if (ar.isError()) {
+                return ar;
+            }
+        }
+        Sim sim = simService.selectSimBySimId(re.getSimId());
+        // 检查模拟器类型
+        {
+            String targetSimType = re.getSimType();
+            AjaxResult ar = commCheckService.checkOneSimType(seat, true, targetSimType);
+            if (ar.isError()) {
+                return ar;
+            }
+        }
+        // Step 5:
+        {
+//            AjaxResult arE3 = commSendService.checkOneSimStateActive(seat);
+//            if (arE3.isError()) {
+//                return arE3;
+//            }
+        }
+        // Step 7:可换件检查,读取对应一台模拟器 所有故障部位值。检查模拟器所有的 真实的 故障部位 是否异常 或者 空值。特殊的故障部位要单独判断。
         if (SimDebugConfig.CHECK_REPLACE_EMPTY) {
-            AjaxResult arE2 = commSendService.readOneSimAllFaultCheck(s);
+            AjaxResult arE2 = commSendService.readOneSimAllFaultCheck(seat, sim);
             if (arE2.isError()) {
                 return arE2;
             }
         }
-        // Step 3 清除对应一台模拟器 所有 真实的 故障部位故障。
+        // Step 8:清除对应一台模拟器 所有 真实的 故障部位故障。
         {
             commSendService.clearOneSimAllFaultByExam(re);
         }
-        // Step 4 下发对应一台模拟器 出题选中的 故障位置故障。
+        // Step 9:下发对应一台模拟器 出题选中的 故障位置故障。
         {
             commSendService.writeOneSimAllSelectFaultByExam(re);
         }
-        // Step 5 读取对应一台模拟器 所有的 真实的 故障部位 电阻值代表值 作为出题值。
+        // Step 10:读取对应一台模拟器 所有的 真实的 故障部位 电阻值代表值 作为出题值。
         // 修改关联状态
         {
             commSendService.readOneSimAllFaultFirstTimeByExam(re);
         }
-        // Step 6 修改当前Exam状态。
+        // Step 11:修改当前exam_id状态。
         if (realExamFaultService.isType2ExamPrepareStartOk(re.getExamId())) {
             updateOneState(re.getExamId(), RealExam.State.SIM_PREPARE_OK);
             updateOneState(re.getExamId(), RealExam.State.ANSWERING);
@@ -358,13 +443,15 @@ public class RealExamService {
         }
         RealExam re = selectRealExamByExamId(examId);
         // 检查 seat_id 是否正确存在
-        {
+        // Java后端处理填写,不检查。
+        if (false) {
             if (!seatService.exist(re.getSeatId())) {
                 return AjaxResult.error("对应座Id不存在!");
             }
         }
+        // Java后端处理填写,不检查。
         // 检查 sim_id 是否正确存在
-        {
+        if (false) {
             if (!simService.existBySimId(re.getSimId())) {
                 return AjaxResult.error("对应模拟器Id不存在!");
             }
@@ -418,7 +505,7 @@ public class RealExamService {
     }
 
     /**
-     * [学生]交卷
+     * [学生]交卷考试
      *
      * @param examId
      * @return RealExam
@@ -438,15 +525,15 @@ public class RealExamService {
             }
         }
         // 检查一下模拟器状态。
-        Sim s = simService.selectSimBySimId(re.getSimId());
+        Seat seat = seatService.selectSeatBySeatId(re.getSeatId());
         // 如果模拟器离线
         {
-            AjaxResult arE3 = commSendService.checkOneSimStateActive(s);
+            // AjaxResult arE3 = commSendService.checkOneSimStateActive(seat);
+            AjaxResult arE3 = commCheckService.checkOneSeatState(seat, true);
             if (arE3.isError()) {
                 return arE3;
             }
         }
-        s = simService.selectSimBySimId(re.getSimId());
         // 最后读取一下模拟器电阻值。
         commSendService.readOneExamAtLast(re);
         if (realExamFaultService.isType2ExamPrepareSubmitOk(re.getExamId())) {

+ 111 - 1
ruoyi-sim/src/main/java/com/ruoyi/sim/service/impl/SeatService.java

@@ -1,8 +1,13 @@
 package com.ruoyi.sim.service.impl;
 
-import java.util.List;
+import java.util.*;
 
+import com.ruoyi.common.core.domain.AjaxResult;
 import com.ruoyi.common.utils.DateUtils;
+import com.ruoyi.sim.domain.Sim;
+import com.ruoyi.sim.domain.vo.SeatVo;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import com.ruoyi.sim.mapper.SeatMapper;
@@ -82,6 +87,8 @@ public class SeatService {
     }
 
     // -------------------------------- tom add  --------------------------------
+    @Autowired
+    private SimService simService;
 
     public boolean exist(Long seatId) {
         if (seatId == null) {
@@ -96,4 +103,107 @@ public class SeatService {
         }
         return true;
     }
+
+    public List<Seat> listAllEnable() {
+        List<Seat> list = new ArrayList<>();
+        seatMapper.selectSeatList(new Seat()).stream().filter(Objects::nonNull).filter(s -> !StringUtils.equals(Seat.SocketState.DISABLE, s.getSeatRs485SocketState())).forEach(list::add);
+        return list;
+    }
+
+    /**
+     * 获取所有没有被禁用的 座 列表
+     *
+     * @return
+     */
+    public AjaxResult listAllEnableAj() {
+        List<Seat> list1 = listAllEnable();
+        List<SeatVo> list2 = new ArrayList<>();
+        for (Seat seat : list1) {
+            SeatVo vo = new SeatVo();
+            BeanUtils.copyProperties(seat, vo);
+            Sim sim = simService.selectSimBySimId(seat.getCurrentSimId());
+            if (seat != null && seat.getCurrentSimId() != null && seat.getCurrentSimId() != 0L) {
+                BeanUtils.copyProperties(sim, vo);
+            }
+            list2.add(vo);
+        }
+        return AjaxResult.success(list2);
+    }
+
+    public Seat uniqueBySeatNum(final Integer seatNum) {
+        Seat q = new Seat();
+        q.setSeatNum(seatNum);
+        List<Seat> list = seatMapper.selectSeatList(q);
+        if (list.isEmpty()) {
+            return null;
+        } else if (list.size() == 1) {
+            return list.get(0);
+        } else {
+            throw new IllegalArgumentException("Seat数据错误。");
+        }
+    }
+
+    public Seat uniqueByBindIp(final String bindIp) {
+        Seat q = new Seat();
+        q.setSeatBindIp(bindIp);
+        List<Seat> list = seatMapper.selectSeatList(q);
+        if (list.isEmpty()) {
+            return null;
+        } else if (list.size() == 1) {
+            return list.get(0);
+        } else {
+            throw new IllegalArgumentException("Seat数据错误。");
+        }
+    }
+
+    public Seat uniqueByRs485IpAndPort(final String rs485Ip, final int rs485Port) {
+        Seat q = new Seat();
+        q.setSeatRs485Ip(rs485Ip);
+        q.setSeatRs485Port(rs485Port);
+        List<Seat> list = seatMapper.selectSeatList(q);
+        if (list.isEmpty()) {
+            return null;
+        } else if (list.size() == 1) {
+            return list.get(0);
+        } else {
+            throw new IllegalArgumentException("Seat数据错误。");
+        }
+    }
+
+    public void updateSimIdBySeatNum(final Integer seatNum, final Long simId) {
+        Seat f = uniqueBySeatNum(seatNum);
+        f.setCurrentSimId(simId);
+        updateSeat(f);
+    }
+
+    public int updateAllEnableState(final String socketState) {
+        List<Seat> list = listAllEnable();
+        for (Seat seat : list) {
+            seat.setSeatRs485SocketState(socketState);
+            seatMapper.updateSeat(seat);
+        }
+        return list.size();
+    }
+
+    public List<String> listAllRs485Ip() {
+        List<Seat> list = listAllEnable();
+        Set<String> ips = new HashSet<>();
+        list.forEach(seat -> ips.add(seat.getSeatRs485Ip()));
+        return ips.stream().toList();
+    }
+
+    public void updateSocketStateBySeatNum(final Integer seatNum, final String socketState) {
+        Seat f = uniqueBySeatNum(seatNum);
+        f.setSeatRs485SocketState(socketState);
+        updateSeat(f);
+    }
+
+    public void updateSocketStateByRs485Ip(final String rs485Ip, final String socketState) {
+        Seat q = new Seat();
+        q.setSeatRs485Ip(rs485Ip);
+        selectSeatList(q).forEach(seat -> {
+            seat.setSeatRs485SocketState(socketState);
+            seatMapper.updateSeat(seat);
+        });
+    }
 }

+ 18 - 8
ruoyi-sim/src/main/java/com/ruoyi/sim/service/impl/SimService.java

@@ -91,7 +91,12 @@ public class SimService {
     }
 
     // -------------------------------- tom add  --------------------------------
+    private static final Logger l = LoggerFactory.getLogger(SimService.class);
 
+    /**
+     * @param simId
+     * @return true:存在,false:不存在
+     */
     public boolean existBySimId(final Long simId) {
         if (simId == null) {
             return false;
@@ -128,10 +133,13 @@ public class SimService {
         Sim q = new Sim();
         q.setSimNum(simNum);
         List<Sim> list = simMapper.selectSimList(q);
-        if (!list.isEmpty()) {
+        if (list.isEmpty()) {
+            return null;
+        } else if (list.size() == 1) {
             return list.get(0);
+        } else {
+            throw new IllegalArgumentException("Sim数据错误。");
         }
-        return null;
     }
 
     public List<Sim> listAll() {
@@ -152,7 +160,7 @@ public class SimService {
         listAll()
                 .stream()
                 .filter(Objects::nonNull)
-                .filter(s -> !Sim.State.DISABLE.equals(s.getSimState()))
+                .filter(s -> !StringUtils.equals(Sim.State.DISABLE, s.getSimState()))
                 .forEach(list::add);
         return list;
     }
@@ -205,16 +213,14 @@ public class SimService {
     }
 
     public boolean checkState(String simState) {
-        return Sim.STATE_SET.contains(simState);
+        return !Sim.STATE_SET.contains(simState);
     }
 
-    private static final Logger l = LoggerFactory.getLogger(SimService.class);
-
     @Transactional
-    public int updateSimStateBySimId(Long simId, String simState) {
+    public int updateSimStateBySimId(final Long simId, final String simState) {
         // check
         if (checkState(simState)) {
-
+            throw new IllegalArgumentException("simState wrong!");
         }
         //
         Sim q = selectSimBySimId(simId);
@@ -225,6 +231,10 @@ public class SimService {
         return updateSim(q);
     }
 
+    public int updateSimStateBySimNum(final String simNum, final String simState) {
+        return updateSimStateBySimId(uniqueBySimNum(simNum).getSimId(), simState);
+    }
+
     public boolean isSimStateBySimId(Long simId, String simState) {
         // check
         if (checkState(simState)) {

+ 87 - 58
ruoyi-sim/src/main/java/com/ruoyi/sim/service/impl/SocketService.java

@@ -2,10 +2,9 @@ package com.ruoyi.sim.service.impl;
 
 import com.ruoyi.common.core.domain.AjaxResult;
 import com.ruoyi.sim.config.SimConfig;
-import com.ruoyi.sim.constant.CommConst;
+import com.ruoyi.sim.domain.Seat;
 import com.ruoyi.sim.domain.vo.SimSocketVo;
 import com.ruoyi.sim.domain.vo.SocketWrapValue;
-import org.apache.commons.lang3.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -14,6 +13,7 @@ import org.springframework.stereotype.Service;
 import java.io.IOException;
 import java.net.Socket;
 import java.util.HashMap;
+import java.util.List;
 
 import static com.ruoyi.sim.constant.CommConst.SOCKET_TIME_OUT;
 
@@ -26,11 +26,13 @@ public class SocketService {
 
     private static final Logger l = LoggerFactory.getLogger(SocketService.class);
 
-    private static final int INIT_SIZE = 16;
+    private static final int INIT_SIZE = 32;
     /**
      * 6 hours
+     * 1000L * 60 * 60 * 6
+     * 1000L * 60 * 5
      */
-    private static final long LIMIT = 1000L * 60 * 60 * 6;
+    private static final long TIMEOUT_LIMIT = 1000L * 60 * 60 * 6;
 
     /**
      * key: ip:port
@@ -42,15 +44,17 @@ public class SocketService {
     private SimConfig config;
     @Autowired
     private FailedCountService failedCountService;
+    @Autowired
+    private SeatService seatService;
 
     /**
-     * @param ip   ip v4.
-     * @param port
+     * @param ssv
      * @return true:socket ok!
      */
-    public boolean isOk(final String ip, final Integer port) {
-        if (cachedMap.containsKey(ip)) {
-            Socket s = cachedMap.get(buildKey(ip, port)).getSocket();
+    public boolean isOk(final SimSocketVo ssv) {
+        final String key = ssv.toKey();
+        if (cachedMap.containsKey(key) && cachedMap.get(key) != null) {
+            Socket s = cachedMap.get(key).getSocket();
             if (s != null) {
                 return (s.isConnected() && s.isBound() && !s.isClosed());
             }
@@ -58,19 +62,26 @@ public class SocketService {
         return false;
     }
 
+    public boolean isNotOk(final SimSocketVo ssv) {
+        return !isOk(ssv);
+    }
+
     /**
-     * @param ip   ip v4.
-     * @param port
+     * 暂时不考虑超时周期。
+     * todo:
+     *
+     * @param ssv
      * @return
      */
-    public String buildKey(final String ip, final Integer port) {
-        if (StringUtils.isBlank(ip)) {
-            throw new IllegalArgumentException("ip error");
-        }
-        if (port == null || port <= CommConst.PORT_MIN || port >= CommConst.PORT_MAX) {
-            throw new IllegalArgumentException("port error");
+    public boolean isTimeout(SimSocketVo ssv) {
+        if (cachedMap.containsKey(ssv.toKey())) {
+            Long cached = cachedMap.get(ssv.toKey()).getOkTimeMillis();
+            if (cached == null || cached == 0L) {
+                return true;
+            }
+            return System.currentTimeMillis() - cached <= TIMEOUT_LIMIT;
         }
-        return ip + ":" + port;
+        return true;
     }
 
     /**
@@ -85,84 +96,102 @@ public class SocketService {
         }
         //
         try {
-            final String ip = ssv.getIp();
-            final Integer port = ssv.getPort();
-            final String key = buildKey(ip, port);
-            if (!isOk(ip, port)) {
-                l.info("openSocket cachedSocket is not ok!new socket ip = {}!", ip);
+            if (isNotOk(ssv)) {
+                final String key = ssv.toKey();
+                l.info("openSocket cachedSocket is not ok!try new socket ip = {}:{}!", ssv.getIp(), ssv.getPort());
                 closeOne(ssv);
-                Socket s = new Socket(ip, port);
+                Socket s = new Socket(ssv.getIp(), ssv.getPort());
                 s.setSoTimeout(SOCKET_TIME_OUT);
-                SocketWrapValue value = new SocketWrapValue(ip, port, s, System.currentTimeMillis());
+                SocketWrapValue value = new SocketWrapValue(ssv.getIp(), ssv.getPort(), s, System.currentTimeMillis());
                 cachedMap.put(key, value);
                 // failed count reset.
-                failedCountService.reset0(key);
+                failedCountService.reset0(ssv);
+            } else {
+                l.info("openSocket cachedSocket cache ok!cached socket ip = {}:{}!", ssv.getIp(), ssv.getPort());
             }
+            Seat seat = seatService.uniqueByRs485IpAndPort(ssv.getIp(), ssv.getPort());
+            seat.setSeatRs485SocketState(Seat.SocketState.ONLINE);
+            seatService.updateSeat(seat);
+            return AjaxResult.success("Socket[" + ssv.getIp() + ":" + ssv.getPort() + "],创建成功!");
         } catch (IOException e) {
-            throw new RuntimeException(e);
-        } finally {
-
+            l.error(ssv.toString());
+            return AjaxResult.error("Socket[" + ssv.getIp() + ":" + ssv.getPort() + "],创建失败!");
         }
-        return AjaxResult.success("openOneSocket Success!");
     }
 
-
-
     /**
      * todo:部分返回Aj结果。
      *
-     * @param ssvs
      * @return
      */
-    public AjaxResult openAll(final SimSocketVo[] ssvs) {
+    public AjaxResult tryOpenAll() {
         if (!config.isCommGlobal()) {
             l.warn("isCommGlobal == {} [模拟器通信被禁用!]", config.isCommGlobal());
             return AjaxResult.error("模拟器通信被禁用!");
         }
-        for (SimSocketVo ssv : ssvs) {
-            openOne(ssv);
+        List<Seat> allSeat = seatService.listAllEnable();
+        for (Seat s : allSeat) {
+            openOne(s.toSimSocketVo());
         }
-        return AjaxResult.success("openAllSocket Success!");
+        return AjaxResult.success("所有Socket,创建成功!");
     }
 
     public AjaxResult closeOne(final SimSocketVo ssv) {
-        String msgOk = "关闭Socket成功!";
         if (!config.isCommGlobal()) {
             l.warn("isCommGlobal == {} [模拟器通信被禁用!]", config.isCommGlobal());
             return AjaxResult.error("模拟器通信被禁用!");
         }
-        final String key = buildKey(ssv.getIp(), ssv.getPort());
-        if (!cachedMap.containsKey(key)) {
-            cachedMap.remove(key);
-            return AjaxResult.success(msgOk);
-        } else {
-            try {
+        String msgOk = "关闭Socket成功!";
+        final String key = ssv.toKey();
+        try {
+            if (cachedMap.containsKey(key)) {
                 Socket s = cachedMap.get(key).getSocket();
                 s.getInputStream().close();
                 s.getOutputStream().close();
                 s.close();
-            } catch (IOException e) {
-                e.printStackTrace();
-            } finally {
-                cachedMap.remove(key);
-                // failed count reset.
-                failedCountService.reset0(key);
-                return AjaxResult.success(msgOk);
             }
+        } catch (IOException e) {
+            e.printStackTrace();
+        } finally {
+            cachedMap.remove(key);
+            // failed count reset.
+            failedCountService.reset0(ssv);
+            return AjaxResult.success(msgOk);
         }
     }
 
-    public AjaxResult closeAll(final SimSocketVo[] ssvs) {
-        String msgOk = "关闭所有Socket成功!";
-        for (SimSocketVo ssv : ssvs) {
-            AjaxResult ar = closeOne(ssv);
-            if (ar != null && !ar.isSuccess()) {
-                return ar;
-            }
+    public AjaxResult closeAll() {
+        if (!config.isCommGlobal()) {
+            l.warn("isCommGlobal == {} [模拟器通信被禁用!]", config.isCommGlobal());
+            return AjaxResult.error("模拟器通信被禁用!");
+        }
+        final String msgOk = "关闭所有Socket成功!";
+        List<Seat> allSeat = seatService.listAllEnable();
+        for (Seat s : allSeat) {
+            closeOne(new SimSocketVo(s.getSeatRs485Ip(), s.getSeatRs485Port()));
         }
+        failedCountService.resetAll();
         return AjaxResult.success(msgOk);
     }
 
+    public Socket get(final Seat seat) {
+        if (seat == null) {
+            throw new IllegalArgumentException("seat is null");
+        }
+        return get(new SimSocketVo(seat.getSeatRs485Ip(), seat.getSeatRs485Port()));
+    }
+
+    /**
+     * @param ssv
+     * @return
+     */
+    public Socket get(final SimSocketVo ssv) {
+        if (isNotOk(ssv)) {
+            openOne(ssv);
+        }
+        return cachedMap.get(ssv.toKey()).getSocket();
+    }
+
     /**
      * 初始化。
      */

+ 7 - 1
ruoyi-sim/src/main/resources/mapper/sim/RealExamMapper.xml

@@ -7,6 +7,7 @@
     <resultMap type="RealExam" id="RealExamResult">
         <result property="examId" column="exam_id"/>
         <result property="examCollectionId" column="exam_collection_id"/>
+        <result property="simType" column="sim_type"/>
         <result property="userId" column="user_id"/>
         <result property="seatId" column="seat_id"/>
         <result property="simId" column="sim_id"/>
@@ -27,6 +28,7 @@
     <sql id="selectRealExamVo">
         select exam_id,
                exam_collection_id,
+               sim_type,
                user_id,
                seat_id,
                sim_id,
@@ -49,6 +51,7 @@
         <include refid="selectRealExamVo"/>
         <where>
             <if test="examCollectionId != null ">and exam_collection_id = #{examCollectionId}</if>
+            <if test="simType != null  and simType != ''">and sim_type = #{simType}</if>
             <if test="userId != null ">and user_id = #{userId}</if>
             <if test="seatId != null ">and seat_id = #{seatId}</if>
             <if test="simId != null ">and sim_id = #{simId}</if>
@@ -68,13 +71,14 @@
     </select>
 
     <insert id="insertRealExam" parameterType="RealExam">
-        <selectKey keyProperty="examId" order="AFTER" resultType="java.lang.Long">
+        <selectKey keyProperty="exam_id" order="AFTER" resultType="java.lang.Long">
             select LAST_INSERT_ID()
         </selectKey>
         insert into mx_real_exam
         <trim prefix="(" suffix=")" suffixOverrides=",">
             <if test="examId != null">exam_id,</if>
             <if test="examCollectionId != null">exam_collection_id,</if>
+            <if test="simType != null">sim_type,</if>
             <if test="userId != null">user_id,</if>
             <if test="seatId != null">seat_id,</if>
             <if test="simId != null">sim_id,</if>
@@ -94,6 +98,7 @@
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="examId != null">#{examId},</if>
             <if test="examCollectionId != null">#{examCollectionId},</if>
+            <if test="simType != null">#{simType},</if>
             <if test="userId != null">#{userId},</if>
             <if test="seatId != null">#{seatId},</if>
             <if test="simId != null">#{simId},</if>
@@ -116,6 +121,7 @@
         update mx_real_exam
         <trim prefix="SET" suffixOverrides=",">
             <if test="examCollectionId != null">exam_collection_id = #{examCollectionId},</if>
+            <if test="simType != null">sim_type = #{simType},</if>
             <if test="userId != null">user_id = #{userId},</if>
             <if test="seatId != null">seat_id = #{seatId},</if>
             <if test="simId != null">sim_id = #{simId},</if>

+ 11 - 1
ruoyi-sim/src/main/resources/mapper/sim/SeatMapper.xml

@@ -10,6 +10,7 @@
         <result property="seatBindIp" column="seat_bind_ip"/>
         <result property="seatRs485Ip" column="seat_rs485_ip"/>
         <result property="seatRs485Port" column="seat_rs485_port"/>
+        <result property="seatRs485SocketState" column="seat_rs485_socket_state"/>
         <result property="currentUserId" column="current_user_id"/>
         <result property="currentSimId" column="current_sim_id"/>
         <result property="createBy" column="create_by"/>
@@ -25,6 +26,7 @@
                seat_bind_ip,
                seat_rs485_ip,
                seat_rs485_port,
+               seat_rs485_socket_state,
                current_user_id,
                current_sim_id,
                create_by,
@@ -42,6 +44,9 @@
             <if test="seatBindIp != null  and seatBindIp != ''">and seat_bind_ip = #{seatBindIp}</if>
             <if test="seatRs485Ip != null  and seatRs485Ip != ''">and seat_rs485_ip = #{seatRs485Ip}</if>
             <if test="seatRs485Port != null ">and seat_rs485_port = #{seatRs485Port}</if>
+            <if test="seatRs485SocketState != null  and seatRs485SocketState != ''">and seat_rs485_socket_state =
+                #{seatRs485SocketState}
+            </if>
             <if test="currentUserId != null ">and current_user_id = #{currentUserId}</if>
             <if test="currentSimId != null ">and current_sim_id = #{currentSimId}</if>
         </where>
@@ -53,7 +58,7 @@
     </select>
 
     <insert id="insertSeat" parameterType="Seat" useGeneratedKeys="true" keyProperty="seatId">
-        <selectKey keyProperty="seatId" order="AFTER" resultType="java.lang.Long">
+        <selectKey keyProperty="seat_id" order="AFTER" resultType="java.lang.Long">
             select LAST_INSERT_ID()
         </selectKey>
         insert into mx_seat
@@ -62,6 +67,7 @@
             <if test="seatBindIp != null and seatBindIp != ''">seat_bind_ip,</if>
             <if test="seatRs485Ip != null and seatRs485Ip != ''">seat_rs485_ip,</if>
             <if test="seatRs485Port != null">seat_rs485_port,</if>
+            <if test="seatRs485SocketState != null and seatRs485SocketState != ''">seat_rs485_socket_state,</if>
             <if test="currentUserId != null">current_user_id,</if>
             <if test="currentSimId != null">current_sim_id,</if>
             <if test="createBy != null">create_by,</if>
@@ -75,6 +81,7 @@
             <if test="seatBindIp != null and seatBindIp != ''">#{seatBindIp},</if>
             <if test="seatRs485Ip != null and seatRs485Ip != ''">#{seatRs485Ip},</if>
             <if test="seatRs485Port != null">#{seatRs485Port},</if>
+            <if test="seatRs485SocketState != null and seatRs485SocketState != ''">#{seatRs485SocketState},</if>
             <if test="currentUserId != null">#{currentUserId},</if>
             <if test="currentSimId != null">#{currentSimId},</if>
             <if test="createBy != null">#{createBy},</if>
@@ -92,6 +99,9 @@
             <if test="seatBindIp != null and seatBindIp != ''">seat_bind_ip = #{seatBindIp},</if>
             <if test="seatRs485Ip != null and seatRs485Ip != ''">seat_rs485_ip = #{seatRs485Ip},</if>
             <if test="seatRs485Port != null">seat_rs485_port = #{seatRs485Port},</if>
+            <if test="seatRs485SocketState != null and seatRs485SocketState != ''">seat_rs485_socket_state =
+                #{seatRs485SocketState},
+            </if>
             <if test="currentUserId != null">current_user_id = #{currentUserId},</if>
             <if test="currentSimId != null">current_sim_id = #{currentSimId},</if>
             <if test="createBy != null">create_by = #{createBy},</if>