RSServerService.java 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427
  1. package com.zksy.environment.config;
  2. import com.zksy.api.domain.WarningThreshold;
  3. import com.zksy.api.service.WarningThresholdService;
  4. import com.zksy.api.utils.SmsUtil;
  5. import com.zksy.environment.domain.ERealTimeData;
  6. import com.zksy.environment.mapper.ERealTimeDataMapper;
  7. import com.zksy.environment.utils.AlarmUtil;
  8. import com.zksy.utils.DevicePhoneFetchUtil;
  9. import lombok.extern.slf4j.Slf4j;
  10. import org.springframework.beans.factory.annotation.Autowired;
  11. import org.springframework.stereotype.Component;
  12. import rk.netDevice.sdk.p2.*;
  13. import javax.annotation.PostConstruct;
  14. import javax.annotation.PreDestroy;
  15. import java.math.BigDecimal;
  16. import java.text.SimpleDateFormat;
  17. import java.time.LocalDateTime;
  18. import java.util.List;
  19. import java.util.concurrent.ExecutorService;
  20. import java.util.concurrent.Executors;
  21. import java.util.concurrent.Future;
  22. import java.util.concurrent.TimeUnit;
  23. @Component
  24. @Slf4j
  25. public class RSServerService {
  26. private RSServer rsServer;
  27. private Future<?> serverFuture;
  28. // 专用线程池:隔离RSServer任务
  29. private final ExecutorService serverExecutor = Executors.newSingleThreadExecutor(r -> {
  30. Thread thread = new Thread(r, "RSServer-Worker");
  31. thread.setDaemon(true);
  32. return thread;
  33. });
  34. @Autowired
  35. private ERealTimeDataMapper realTimeDataMapper;
  36. @Autowired
  37. private WarningThresholdService warningThresholdService;
  38. @Autowired
  39. private SmsUtil smsUtil;
  40. @Autowired
  41. private DevicePhoneFetchUtil devicePhoneFetchUtil;
  42. @Autowired
  43. private AlarmUtil alarmUtil;
  44. @PostConstruct
  45. public void init() {
  46. log.info("RSServer服务开始初始化,监听端口:9801");
  47. // 提交初始化任务 + 启动监控
  48. serverFuture = serverExecutor.submit(this::initializeServer);
  49. new Thread(this::monitorServerInitialization, "RSServer-Monitor").start();
  50. }
  51. /**
  52. * 监控线程
  53. */
  54. private void monitorServerInitialization() {
  55. if (serverFuture == null) {
  56. log.error("RSServer初始化任务未提交,监控终止");
  57. return;
  58. }
  59. try {
  60. long timeoutSeconds = 20;
  61. serverFuture.get(timeoutSeconds, TimeUnit.SECONDS);
  62. log.info("RSServer初始化任务正常完成");
  63. } catch (java.util.concurrent.TimeoutException e) {
  64. log.warn("RSServer初始化超时(>20秒),服务器可能仍在启动(正常慢启动),可延长timeoutSeconds阈值");
  65. } catch (Exception e) {
  66. log.error("RSServer初始化发生异常(非超时)", e);
  67. if (!serverFuture.isDone()) {
  68. serverFuture.cancel(true);
  69. log.warn("已强制取消异常初始化任务");
  70. }
  71. }
  72. }
  73. private void initializeServer() {
  74. try {
  75. long initiateStartTime = System.currentTimeMillis();
  76. rsServer = RSServer.Initiate(9801);
  77. log.info("RSServer实例初始化完成,耗时:{}ms", System.currentTimeMillis() - initiateStartTime);
  78. rsServer.addDataListener(new IDataListener() {
  79. @Override
  80. public void receiveTimmingAck(TimmingAck data) {
  81. System.out.println("校时应答->设备编号:" + data.getDeviceId()
  82. + "\t执行结果:" + data.getStatus());
  83. }
  84. @Override
  85. public void receiveTelecontrolAck(TelecontrolAck data) {
  86. System.out.println("遥控应答->设备编号:" + data.getDeviceId()
  87. + "\t继电器编号:" + data.getRelayId() + "\t执行结果:"
  88. + data.getStatus());
  89. }
  90. @Override
  91. public void receiveStoreData(StoreData data) {
  92. for (NodeData nd : data.getNodeList()) {
  93. SimpleDateFormat sdf = new SimpleDateFormat(
  94. "yy-MM-dd HH:mm:ss");
  95. String str = sdf.format(nd.getRecordTime());
  96. System.out.println("存储数据->设备地址:" + data.getDeviceId()
  97. + "\t节点:" + nd.getNodeId() + "\t温度:" + nd.getTem()
  98. + "\t湿度:" + nd.getHum() + "\t存储时间:" + str);
  99. }
  100. }
  101. @Override
  102. public void receiveRealtimeData(RealTimeData data) {
  103. for (NodeData nd : data.getNodeList()) {
  104. try {
  105. // 获取当前节点ID
  106. Integer nodeId = nd.getNodeId();
  107. // 定义最终要存储的数值
  108. float finalValue;
  109. // 字段赋值标记:true=存floatValue(悬浮物),false=存hum(湿度)
  110. boolean saveAsFloatValue = false;
  111. // ====================== 按节点处理数值 ======================
  112. if (nodeId == 1) {
  113. // 节点1:悬浮物,系数10
  114. finalValue = nd.getFloatValue() * 10;
  115. saveAsFloatValue = true;
  116. } else if (nodeId == 2) {
  117. // 节点2:湿度,系数1
  118. finalValue = nd.getHum() * 1;
  119. } else if (nodeId == 3) {
  120. // 节点3:湿度,系数0.1
  121. finalValue = nd.getHum() * 0.1f;
  122. } else if (nodeId == 4) {
  123. // 节点4:湿度,系数10
  124. finalValue = nd.getHum() * 10;
  125. } else if (nodeId == 5) {
  126. // 节点5:湿度,系数0.1
  127. finalValue = nd.getHum() * 0.1f;
  128. } else {
  129. // 未知节点,跳过处理
  130. log.warn("未知节点ID:{},不进行数据处理", nodeId);
  131. continue;
  132. }
  133. // 保留两位小数(核心处理)
  134. finalValue = Math.round(finalValue * 100) / 100.0f;
  135. // ====================== 封装实体 ======================
  136. ERealTimeData realTimeData = new ERealTimeData();
  137. realTimeData.setDeviceId(data.getDeviceId());
  138. realTimeData.setNodeId(nodeId);
  139. realTimeData.setTem(nd.getTem());
  140. realTimeData.setLng(nd.getLng());
  141. realTimeData.setLat(nd.getLat());
  142. realTimeData.setCoordinateType(String.valueOf(data.getCoordinateType()));
  143. realTimeData.setRelayStatus(String.valueOf(data.getRelayStatus()));
  144. realTimeData.setCreateTime(LocalDateTime.now());
  145. // 按节点类型赋值对应字段
  146. if (saveAsFloatValue) {
  147. // 节点1:存入悬浮物
  148. realTimeData.setFloatValue(String.valueOf(finalValue));
  149. // 节点1不需要湿度,清空避免脏数据
  150. realTimeData.setHum(0.0f);
  151. } else {
  152. // 节点2-5:存入湿度
  153. realTimeData.setHum(finalValue);
  154. // 非节点1清空浮点值
  155. realTimeData.setFloatValue("0.00");
  156. }
  157. // 数据库插入
  158. realTimeDataMapper.insert(realTimeData);
  159. // ====================== 报警处理 ======================
  160. String deviceId = String.valueOf(data.getDeviceId());
  161. // 悬浮物判断
  162. if (!"".equals(realTimeData.getFloatValue()) && nd.getNodeId() == 1) {
  163. String temWarningCode = "WARN-SUSPENDED-SOLIDS";
  164. WarningThreshold temThreshold = checkThreshold(deviceId, temWarningCode);
  165. Double temMinValue = temThreshold != null ? temThreshold.getMinValue() : null;
  166. Double temMaxValue = temThreshold != null ? temThreshold.getMaxValue() : null;
  167. String temWarningType = temThreshold != null ? temThreshold.getWarningType() : "悬浮物预警";
  168. String temRemark = temThreshold != null ? temThreshold.getRemark() : "环境悬浮物报警";
  169. boolean temShouldAlarm = false;
  170. BigDecimal temValue = BigDecimal.valueOf(nd.getTem());
  171. if (temMinValue != null && nd.getTem() <= temMinValue) {
  172. temShouldAlarm = true;
  173. }
  174. if (temMaxValue != null && nd.getTem() >= temMaxValue) {
  175. temShouldAlarm = true;
  176. }
  177. if (temShouldAlarm) {
  178. alarmUtil.saveAlarm(deviceId, temWarningType, temWarningCode,
  179. temMinValue != null ? BigDecimal.valueOf(temMinValue) : null,
  180. temMaxValue != null ? BigDecimal.valueOf(temMaxValue) : null,
  181. temValue, temRemark);
  182. }
  183. }
  184. // 只有非节点1才判断湿度报警(节点1无湿度数据)
  185. if (nodeId == 2 && !Float.isNaN(nd.getHum())) {
  186. String humWarningCode = "WARN-HUMIDITY";
  187. WarningThreshold humThreshold = checkThreshold(deviceId, humWarningCode);
  188. Double humMinValue = humThreshold != null ? humThreshold.getMinValue() : null;
  189. Double humMaxValue = humThreshold != null ? humThreshold.getMaxValue() : 90.0;
  190. String humWarningType = humThreshold != null ? humThreshold.getWarningType() : "湿度预警";
  191. String humRemark = humThreshold != null ? humThreshold.getRemark() : "环境湿度报警";
  192. boolean humShouldAlarm = false;
  193. BigDecimal humValue = BigDecimal.valueOf(finalValue);
  194. if (humMinValue != null && finalValue <= humMinValue) {
  195. humShouldAlarm = true;
  196. }
  197. if (humMaxValue != null && finalValue >= humMaxValue) {
  198. humShouldAlarm = true;
  199. }
  200. if (humShouldAlarm) {
  201. alarmUtil.saveAlarm(deviceId, humWarningType, humWarningCode,
  202. humMinValue != null ? BigDecimal.valueOf(humMinValue) : null,
  203. humMaxValue != null ? BigDecimal.valueOf(humMaxValue) : null,
  204. humValue, humRemark);
  205. }
  206. }
  207. if (nodeId == 3 && !Float.isNaN(nd.getHum())) {
  208. String humWarningCode = "WARN-AMMONIA-NITROGEN";
  209. WarningThreshold humThreshold = checkThreshold(deviceId, humWarningCode);
  210. Double humMinValue = humThreshold != null ? humThreshold.getMinValue() : null;
  211. Double humMaxValue = humThreshold != null ? humThreshold.getMaxValue() : null;
  212. String humWarningType = humThreshold != null ? humThreshold.getWarningType() : "氨氮预警";
  213. String humRemark = humThreshold != null ? humThreshold.getRemark() : "环境氨氮报警";
  214. boolean humShouldAlarm = false;
  215. BigDecimal humValue = BigDecimal.valueOf(finalValue);
  216. if (humMinValue != null && finalValue <= humMinValue) {
  217. humShouldAlarm = true;
  218. }
  219. if (humMaxValue != null && finalValue >= humMaxValue) {
  220. humShouldAlarm = true;
  221. }
  222. if (humShouldAlarm) {
  223. alarmUtil.saveAlarm(deviceId, humWarningType, humWarningCode,
  224. humMinValue != null ? BigDecimal.valueOf(humMinValue) : null,
  225. humMaxValue != null ? BigDecimal.valueOf(humMaxValue) : null,
  226. humValue, humRemark);
  227. }
  228. }
  229. if (nodeId == 4 && !Float.isNaN(nd.getHum())) {
  230. String humWarningCode = "WARN-CONDUCTIVITY";
  231. WarningThreshold humThreshold = checkThreshold(deviceId, humWarningCode);
  232. Double humMinValue = humThreshold != null ? humThreshold.getMinValue() : null;
  233. Double humMaxValue = humThreshold != null ? humThreshold.getMaxValue() : null;
  234. String humWarningType = humThreshold != null ? humThreshold.getWarningType() : "电导率预警";
  235. String humRemark = humThreshold != null ? humThreshold.getRemark() : "环境电导率报警";
  236. boolean humShouldAlarm = false;
  237. BigDecimal humValue = BigDecimal.valueOf(finalValue);
  238. if (humMinValue != null && finalValue <= humMinValue) {
  239. humShouldAlarm = true;
  240. }
  241. if (humMaxValue != null && finalValue >= humMaxValue) {
  242. humShouldAlarm = true;
  243. }
  244. if (humShouldAlarm) {
  245. alarmUtil.saveAlarm(deviceId, humWarningType, humWarningCode,
  246. humMinValue != null ? BigDecimal.valueOf(humMinValue) : null,
  247. humMaxValue != null ? BigDecimal.valueOf(humMaxValue) : null,
  248. humValue, humRemark);
  249. }
  250. }
  251. if (nodeId == 5 && !Float.isNaN(nd.getHum())) {
  252. String humWarningCode = "WARN-PH";
  253. WarningThreshold humThreshold = checkThreshold(deviceId, humWarningCode);
  254. Double humMinValue = humThreshold != null ? humThreshold.getMinValue() : null;
  255. Double humMaxValue = humThreshold != null ? humThreshold.getMaxValue() : null;
  256. String humWarningType = humThreshold != null ? humThreshold.getWarningType() : "PH预警";
  257. String humRemark = humThreshold != null ? humThreshold.getRemark() : "环境PH报警";
  258. boolean humShouldAlarm = false;
  259. BigDecimal humValue = BigDecimal.valueOf(finalValue);
  260. if (humMinValue != null && finalValue <= humMinValue) {
  261. humShouldAlarm = true;
  262. }
  263. if (humMaxValue != null && finalValue >= humMaxValue) {
  264. humShouldAlarm = true;
  265. }
  266. if (humShouldAlarm) {
  267. alarmUtil.saveAlarm(deviceId, humWarningType, humWarningCode,
  268. humMinValue != null ? BigDecimal.valueOf(humMinValue) : null,
  269. humMaxValue != null ? BigDecimal.valueOf(humMaxValue) : null,
  270. humValue, humRemark);
  271. }
  272. }
  273. // 获取手机号并发送短信
  274. List<String> devicePhoneList = devicePhoneFetchUtil.getPhoneListByDeviceId(deviceId);
  275. smsUtil.checkDeviceAlarmAndSend(data, nd, devicePhoneList);
  276. } catch (Exception e) {
  277. log.error("实时数据入库失败:设备ID={}, 节点ID={}", data.getDeviceId(), nd.getNodeId(), e);
  278. }
  279. }
  280. }
  281. @Override
  282. public void receiveLoginData(LoginData data) {
  283. System.out.println("登录->设备地址:" + data.getDeviceId());
  284. }
  285. @Override
  286. public void receiveParamIds(ParamIdsData data) {
  287. String str = "设备参数编号列表->设备编号:" + data.getDeviceId()
  288. + "\t参数总数量:" + data.getTotalCount() + "\t本帧参数数量:"
  289. + data.getCount() + "\r\n";
  290. for (int paramId : data.getPararmIdList())
  291. {
  292. str += paramId + ",";
  293. }
  294. System.out.println(str);
  295. }
  296. @Override
  297. public void receiveParam(ParamData data) {
  298. String str = "设备参数->设备编号:" + data.getDeviceId() + "\r\n";
  299. for (ParamItem pararm : data.getParameterList()) {
  300. str += "参数编号:"
  301. + pararm.getParamId()
  302. + "\t参数描述:"
  303. + pararm.getDescription()
  304. + "\t参数值:"
  305. + (pararm.getValueDescription() == null ? pararm
  306. .getValue() : pararm.getValueDescription()
  307. .get(pararm.getValue())) + "\r\n";
  308. }
  309. System.out.println(str);
  310. }
  311. @Override
  312. public void receiveWriteParamAck(WriteParamAck data) {
  313. String str = "下载设备参数->设备编号:" + data.getDeviceId() + "\t参数数量:"
  314. + data.getCount() + "\t"
  315. + (data.isSuccess() ? "下载成功" : "下载失败");
  316. System.out.println(str);
  317. }
  318. @Override
  319. public void receiveTransDataAck(TransDataAck data) {
  320. String str = "数据透传->设备编号:" + data.getDeviceId() + "\t响应结果:"
  321. + data.getData() + "\r\n字节数:" + data.getTransDataLen();
  322. System.out.println(str);
  323. }
  324. @Override
  325. public void receiveHeartbeatData(HeartbeatData heartbeatData) {
  326. }
  327. });
  328. log.info("开始启动RSServer(阻塞式监听)");
  329. long startTime = System.currentTimeMillis();
  330. rsServer.start(); // 正常运行时不返回,停止时执行后续日志
  331. log.info("RSServer已启动并监听端口:9801,启动耗时:{}ms", System.currentTimeMillis() - startTime);
  332. } catch (Exception e) {
  333. log.error("RSServer初始化/启动异常", e);
  334. throw new RuntimeException("RSServer启动失败", e);
  335. }
  336. }
  337. /**
  338. * 关闭
  339. */
  340. @PreDestroy
  341. public void destroy() {
  342. log.info("开始关闭RSServer资源");
  343. // 1. 停止服务器
  344. if (rsServer != null) {
  345. try {
  346. rsServer.stop();
  347. log.info("RSServer服务器已停止");
  348. } catch (Exception e) {
  349. log.error("停止RSServer时发生异常", e);
  350. }
  351. }
  352. // 2. 关闭线程池
  353. serverExecutor.shutdown();
  354. try {
  355. if (!serverExecutor.awaitTermination(5, TimeUnit.SECONDS)) {
  356. serverExecutor.shutdownNow();
  357. log.warn("RSServer线程池优雅关闭超时,已强制关闭");
  358. } else {
  359. log.info("RSServer线程池已正常关闭");
  360. }
  361. } catch (InterruptedException e) {
  362. serverExecutor.shutdownNow();
  363. log.warn("关闭线程池时被中断", e);
  364. Thread.currentThread().interrupt();
  365. }
  366. log.info("RSServer资源关闭完成");
  367. }
  368. private WarningThreshold checkThreshold(String deviceCode, String warningCode) {
  369. try {
  370. return warningThresholdService.getWarningThresholdByDeviceAndCode(deviceCode, warningCode);
  371. } catch (Exception e) {
  372. log.error("查询预警阈值失败:deviceCode={}, warningCode={}", deviceCode, warningCode, e);
  373. return null;
  374. }
  375. }
  376. }