859 lines
41 KiB
Java
859 lines
41 KiB
Java
package com.sipai.config;
|
||
|
||
import com.alibaba.fastjson.JSON;
|
||
import com.alibaba.fastjson.JSONArray;
|
||
import com.alibaba.fastjson.JSONException;
|
||
import com.alibaba.fastjson.JSONObject;
|
||
import com.sipai.entity.data.DeviceData;
|
||
import com.sipai.entity.data.PipelineEquipment;
|
||
import com.sipai.entity.data.PipelineEquipmentMpoint;
|
||
import com.sipai.entity.data.SensorData;
|
||
import com.sipai.entity.mqtt.MqttConfig;
|
||
import com.sipai.entity.scada.MPoint;
|
||
import com.sipai.entity.scada.MPointES;
|
||
import com.sipai.entity.scada.MPointHistory;
|
||
import com.sipai.service.data.PipelineEquipmentMpointService;
|
||
import com.sipai.service.data.PipelineEquipmentService;
|
||
import com.sipai.service.mqtt.MqttConfigService;
|
||
import com.sipai.service.scada.MPointHistoryService;
|
||
import com.sipai.service.scada.MPointService;
|
||
import com.sipai.tools.AlarmType;
|
||
import com.sipai.tools.CommString;
|
||
import com.sipai.tools.CommUtil;
|
||
import io.netty.buffer.ByteBuf;
|
||
import io.netty.buffer.Unpooled;
|
||
import org.redisson.api.RBatch;
|
||
import org.redisson.api.RMapCache;
|
||
import org.redisson.api.RedissonClient;
|
||
import org.slf4j.Logger;
|
||
import org.slf4j.LoggerFactory;
|
||
import org.springframework.beans.factory.annotation.Autowired;
|
||
import org.springframework.beans.factory.annotation.Value;
|
||
import org.springframework.boot.CommandLineRunner;
|
||
import org.springframework.stereotype.Component;
|
||
|
||
import java.io.IOException;
|
||
import java.math.BigDecimal;
|
||
import java.net.DatagramPacket;
|
||
import java.net.DatagramSocket;
|
||
import java.nio.charset.StandardCharsets;
|
||
import java.nio.file.Files;
|
||
import java.nio.file.Path;
|
||
import java.nio.file.Paths;
|
||
import java.text.ParseException;
|
||
import java.text.SimpleDateFormat;
|
||
import java.time.LocalDateTime;
|
||
import java.time.format.DateTimeFormatter;
|
||
import java.util.*;
|
||
import java.util.concurrent.ConcurrentHashMap;
|
||
import java.util.concurrent.TimeUnit;
|
||
|
||
/**
|
||
* 南昌项目upd接收数据处理
|
||
* sj 2025-05-21
|
||
*/
|
||
@Component
|
||
public class UDPServer implements CommandLineRunner {
|
||
|
||
@Value("${udp.server.port}") //端口
|
||
private int port;
|
||
@Value("${udp.server.enabled:true}") // 默认值为true
|
||
private boolean enabled;
|
||
private static final int BUFFER_SIZE = 1024;
|
||
|
||
@Autowired
|
||
private PipelineEquipmentService pipelineEquipmentService;
|
||
@Autowired
|
||
private PipelineEquipmentMpointService pipelineEquipmentMpointService;
|
||
@Autowired
|
||
private MPointService mPointService;
|
||
@Autowired
|
||
private MPointHistoryService mPointHistoryService;
|
||
@Autowired
|
||
private MqttConfigService mqttConfigService;
|
||
|
||
//缓存点位
|
||
private static final Map<String, String> dataCache = new ConcurrentHashMap<>();
|
||
|
||
@Override
|
||
public void run(String... args) throws Exception {
|
||
|
||
if (!enabled) {
|
||
System.out.println("UDP服务未启用");
|
||
return;
|
||
}
|
||
|
||
try (DatagramSocket socket = new DatagramSocket(port)) {
|
||
System.out.println("UDP服务端启动,监听端口: " + port);
|
||
byte[] buffer = new byte[BUFFER_SIZE];
|
||
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
|
||
while (true) {
|
||
socket.receive(packet);
|
||
// System.out.println(CommUtil.nowDate() + "======收到来自 " + packet.getAddress() + ":" + packet.getPort() + " 的数据");
|
||
|
||
// 发送应答
|
||
String response = "@222\r\n";
|
||
byte[] responseData = response.getBytes(StandardCharsets.UTF_8);
|
||
DatagramPacket responsePacket = new DatagramPacket(
|
||
responseData, responseData.length,
|
||
packet.getAddress(), packet.getPort());
|
||
socket.send(responsePacket);
|
||
|
||
// 解析数据包
|
||
byte[] receivedData = new byte[packet.getLength()];
|
||
System.arraycopy(packet.getData(), 0, receivedData, 0, packet.getLength());
|
||
// 打印原始字节数组(十六进制格式)
|
||
// System.out.println("原始数据包(十六进制): " + bytesToHex(receivedData));
|
||
|
||
DeviceData data = parseDeviceData(Unpooled.wrappedBuffer(receivedData));
|
||
if (data != null) {
|
||
String jsonData = CommUtil.toJson(data);
|
||
JSONObject jsonObject = JSON.parseObject(jsonData);
|
||
System.out.println("站号收到数据:" + jsonObject.get("stationId")+":"+jsonObject.get("startTime"));
|
||
JSONArray jsonArray = JSONArray.parseArray(jsonObject.get("sensorDataList").toString());
|
||
if (jsonArray != null && !jsonArray.isEmpty()) {
|
||
processSensorData(jsonObject, jsonArray, bytesToHex(receivedData));
|
||
}
|
||
}
|
||
}
|
||
} catch (IOException e) {
|
||
Logger logger = LoggerFactory.getLogger(UDPServer.class);
|
||
logger.error("UDP服务端启动异常", e);
|
||
e.printStackTrace();
|
||
}
|
||
}
|
||
|
||
//每次收到的报文存到D盘下 - 用于调试
|
||
private void logs(String type, String bytes, String path) {
|
||
try {
|
||
// 获取当前时间
|
||
LocalDateTime now = LocalDateTime.now();
|
||
|
||
// 定义文件名(保持原来的格式)
|
||
String fileName = DateTimeFormatter
|
||
.ofPattern("yyyyMMdd_HHmmss")
|
||
.format(now) + "_" + type + ".txt";
|
||
|
||
// 定义文件路径(D:/data_logs/年-月-日/自定义路径/)
|
||
String dateDir = DateTimeFormatter.ofPattern("yyyy-MM-dd").format(now);
|
||
Path dirPath = Paths.get("D:/data_logs_new/" + dateDir + "/" + path + "/");
|
||
|
||
if (!Files.exists(dirPath)) {
|
||
Files.createDirectories(dirPath); // 如果目录不存在,则创建
|
||
}
|
||
|
||
Path filePath = dirPath.resolve(fileName); // 完整路径
|
||
Files.write(filePath, bytes.getBytes()); // 写入内容到文件
|
||
} catch (IOException e) {
|
||
e.printStackTrace();
|
||
}
|
||
}
|
||
|
||
//分发到各个业务处理
|
||
private void processSensorData(JSONObject jsonObject, JSONArray jsonArray, String bytes) {
|
||
String stationId = jsonObject.get("stationId").toString();
|
||
switch (stationId.charAt(1)) {
|
||
case '1'://井盖
|
||
logs(stationId, bytes, "jinggai");
|
||
processManholeCoverData(jsonObject, jsonArray);
|
||
break;
|
||
case '2'://流量计
|
||
logs(stationId, bytes, "liuliang");
|
||
processFlowMeterData(jsonObject, jsonArray);
|
||
break;
|
||
case '3'://液位计
|
||
logs(stationId, bytes, "yewei");
|
||
processLevelMeterData(jsonObject, jsonArray);
|
||
break;
|
||
case '4'://电导率
|
||
logs(stationId, bytes, "diandao");
|
||
processConductivityMeterData(jsonObject, jsonArray);
|
||
break;
|
||
case '5'://雨量计
|
||
logs(stationId, bytes, "yuliang");
|
||
processRainGaugeData(jsonObject, jsonArray);
|
||
break;
|
||
case '6'://内涝监测
|
||
logs(stationId, bytes, "neilao");
|
||
processWaterloggingMonitoringData(jsonObject, jsonArray);
|
||
break;
|
||
default:
|
||
System.out.println("未知设备类型");
|
||
}
|
||
}
|
||
|
||
// 井盖处理逻辑
|
||
private void processManholeCoverData(JSONObject jsonObject, JSONArray jsonArray) {
|
||
// System.out.println("开始处理井盖数据==============================================");
|
||
int dataCount = jsonObject.getIntValue("dataCount");
|
||
String startTimeStr = jsonObject.getString("startTime");
|
||
int intervalMinutes = jsonObject.getIntValue("intervalMinutes");
|
||
String stationId = jsonObject.getString("stationId");
|
||
double batteryVoltage = jsonObject.getDouble("batteryVoltage"); // 获取电池电压
|
||
|
||
// System.out.println("数据个数:" + dataCount);
|
||
// System.out.println("开始时间:" + startTimeStr);
|
||
// System.out.println("间隔分钟:" + intervalMinutes);
|
||
|
||
//单独处理电压
|
||
String channelName = stationId + "_V1";
|
||
String mpcode = getMpcodeFromCacheOrDB(stationId, channelName, "V1");
|
||
if (!mpcode.equals("未配置")) {
|
||
//推送kafka
|
||
mPointService.sendKafka4UDP("",mpcode, batteryVoltage, startTimeStr, 0, 1);
|
||
//推送mqtt
|
||
JSONArray jsonArray_mqtt = new JSONArray();
|
||
JSONObject jsonObject_mqtt = new JSONObject();
|
||
jsonObject_mqtt.put("mpcode", mpcode);
|
||
jsonObject_mqtt.put("value", batteryVoltage);
|
||
jsonObject_mqtt.put("time", startTimeStr);
|
||
jsonArray_mqtt.add(jsonObject_mqtt);
|
||
mqttConfigService.doSendView(jsonArray_mqtt,CommString.Mqtt_Topic_DATA);
|
||
}
|
||
|
||
// 生成时间点序列
|
||
List<String> timePoints = generateTimePoints(startTimeStr, dataCount, intervalMinutes);
|
||
|
||
// 处理模拟量数据 (type=4)
|
||
// System.out.println("\n模拟量数据:");
|
||
Map<Integer, String> analogMappings = new HashMap<>();
|
||
analogMappings.put(1, "A1"); // 信号强度
|
||
analogMappings.put(2, "A2"); // 基站序号
|
||
analogMappings.put(3, "A3"); // 井盖角度
|
||
analogMappings.put(4, "A4"); // 电阻值1
|
||
analogMappings.put(5, "A5"); // 电阻值2
|
||
processSensorData(stationId, jsonArray, 4, analogMappings, "analogValues", timePoints);
|
||
|
||
// 创建新的JSONArray来存储处理后的报警数据
|
||
JSONArray processedAlarmData = new JSONArray();
|
||
|
||
// 处理报警量数据 (type=5)
|
||
// System.out.println("\n报警量数据:");
|
||
for (Object obj : jsonArray) {
|
||
JSONObject sensorData = (JSONObject) obj;
|
||
if (sensorData.getIntValue("type") == 5) {
|
||
int qValue = sensorData.getIntValue("qValue");
|
||
|
||
// 提取各报警位状态(0/1)
|
||
int voltageAlarm = (qValue & 0x0001) != 0 ? 1 : 0; // Q10
|
||
int waterAlarm = (qValue & 0x0002) != 0 ? 1 : 0; // Q11
|
||
int coverAlarm = (qValue & 0x0004) != 0 ? 1 : 0; // Q12
|
||
|
||
// 为每个报警位创建单独的数据对象
|
||
JSONObject voltageAlarmData = new JSONObject();
|
||
voltageAlarmData.put("type", 5);
|
||
voltageAlarmData.put("channel", 0); // Q10对应channel 0
|
||
voltageAlarmData.put("qValues", Collections.nCopies(dataCount, voltageAlarm));
|
||
|
||
JSONObject waterAlarmData = new JSONObject();
|
||
waterAlarmData.put("type", 5);
|
||
waterAlarmData.put("channel", 1); // Q11对应channel 1
|
||
waterAlarmData.put("qValues", Collections.nCopies(dataCount, waterAlarm));
|
||
|
||
JSONObject coverAlarmData = new JSONObject();
|
||
coverAlarmData.put("type", 5);
|
||
coverAlarmData.put("channel", 2); // Q12对应channel 2
|
||
coverAlarmData.put("qValues", Collections.nCopies(dataCount, coverAlarm));
|
||
|
||
// 添加到处理后的报警数据数组
|
||
processedAlarmData.add(voltageAlarmData);
|
||
processedAlarmData.add(waterAlarmData);
|
||
processedAlarmData.add(coverAlarmData);
|
||
} else {
|
||
// 非报警数据直接保留
|
||
processedAlarmData.add(sensorData);
|
||
}
|
||
}
|
||
|
||
// 处理报警数据存储
|
||
Map<Integer, String> alarmMappings = new HashMap<>();
|
||
alarmMappings.put(0, "Q10"); // 电压报警
|
||
alarmMappings.put(1, "Q11"); // 水浸报警
|
||
alarmMappings.put(2, "Q12"); // 开盖报警
|
||
processSensorData(stationId, processedAlarmData, 5, alarmMappings, "qValues", timePoints);
|
||
}
|
||
|
||
// 流量计处理逻辑
|
||
private void processFlowMeterData(JSONObject jsonObject, JSONArray jsonArray) {
|
||
// System.out.println("开始处理流量计数据==============================================");
|
||
int dataCount = jsonObject.getIntValue("dataCount");
|
||
String startTimeStr = jsonObject.getString("startTime");
|
||
int intervalMinutes = jsonObject.getIntValue("intervalMinutes");
|
||
String stationId = jsonObject.getString("stationId");
|
||
|
||
// System.out.println("数据个数:" + dataCount);
|
||
// System.out.println("开始时间:" + startTimeStr);
|
||
// System.out.println("间隔分钟:" + intervalMinutes);
|
||
|
||
// 生成时间点序列
|
||
List<String> timePoints = generateTimePoints(startTimeStr, dataCount, intervalMinutes);
|
||
|
||
// 处理脉冲量数据 (type=1)
|
||
// System.out.println("\n脉冲量数据:");
|
||
Map<Integer, String> pulseMappings = new HashMap<>();
|
||
pulseMappings.put(1, "P1");
|
||
pulseMappings.put(2, "P2");
|
||
pulseMappings.put(3, "P3");
|
||
processSensorData(stationId, jsonArray, 1, pulseMappings, "accumulatedFlows", timePoints);
|
||
|
||
// 处理模拟量数据 (type=4)
|
||
// System.out.println("\n模拟量数据:");
|
||
Map<Integer, String> analogMappings = new HashMap<>();
|
||
analogMappings.put(1, "A1");
|
||
analogMappings.put(2, "A2");
|
||
analogMappings.put(4, "A4");
|
||
analogMappings.put(7, "A7");
|
||
analogMappings.put(8, "A8");
|
||
processSensorData(stationId, jsonArray, 4, analogMappings, "analogValues", timePoints);
|
||
}
|
||
|
||
// 液位计处理逻辑
|
||
private void processLevelMeterData(JSONObject jsonObject, JSONArray jsonArray) {
|
||
// System.out.println("开始处理液位计数据==============================================");
|
||
int dataCount = jsonObject.getIntValue("dataCount");
|
||
String startTimeStr = jsonObject.getString("startTime");
|
||
int intervalMinutes = jsonObject.getIntValue("intervalMinutes");
|
||
String stationId = jsonObject.getString("stationId");
|
||
|
||
// System.out.println("数据个数:" + dataCount);
|
||
// System.out.println("开始时间:" + startTimeStr);
|
||
// System.out.println("间隔分钟:" + intervalMinutes);
|
||
|
||
// 生成时间点序列
|
||
List<String> timePoints = generateTimePoints(startTimeStr, dataCount, intervalMinutes);
|
||
|
||
// 处理模拟量数据 (type=4)
|
||
// System.out.println("\n液位计模拟量数据:");
|
||
Map<Integer, String> analogMappings = new HashMap<>();
|
||
analogMappings.put(1, "A1"); // 信号接收功率(dBm)
|
||
analogMappings.put(2, "A2"); // 信噪比
|
||
analogMappings.put(3, "A3"); // 小区ID
|
||
analogMappings.put(4, "A4"); // 倾斜角度(°)
|
||
analogMappings.put(5, "A5"); // 电池电压(V)
|
||
analogMappings.put(6, "A6"); // 投入式液位(m)
|
||
analogMappings.put(8, "A8"); // 雷达水位(m)
|
||
analogMappings.put(10, "A10"); // 综合液位(m)
|
||
|
||
processSensorData(stationId, jsonArray, 4, analogMappings, "analogValues", timePoints);
|
||
}
|
||
|
||
// 电导率仪处理逻辑
|
||
private void processConductivityMeterData(JSONObject jsonObject, JSONArray jsonArray) {
|
||
// System.out.println("开始处理电导率仪数据==============================================");
|
||
int dataCount = jsonObject.getIntValue("dataCount");
|
||
String startTimeStr = jsonObject.getString("startTime");
|
||
int intervalMinutes = jsonObject.getIntValue("intervalMinutes");
|
||
String stationId = jsonObject.getString("stationId");
|
||
double batteryVoltage = jsonObject.getDouble("batteryVoltage");
|
||
// System.out.println("数据个数:" + dataCount);
|
||
// System.out.println("开始时间:" + startTimeStr);
|
||
// System.out.println("间隔分钟:" + intervalMinutes);
|
||
|
||
//单独处理电压
|
||
String channelName = stationId + "_V";
|
||
String mpcode = getMpcodeFromCacheOrDB(stationId, channelName, "V");
|
||
if (!mpcode.equals("未配置")) {
|
||
mPointService.sendKafka4UDP("",mpcode, batteryVoltage, startTimeStr, 0, 1);
|
||
//推送mqtt
|
||
JSONArray jsonArray_mqtt = new JSONArray();
|
||
JSONObject jsonObject_mqtt = new JSONObject();
|
||
jsonObject_mqtt.put("mpcode", mpcode);
|
||
jsonObject_mqtt.put("value", batteryVoltage);
|
||
jsonObject_mqtt.put("time", startTimeStr);
|
||
jsonArray_mqtt.add(jsonObject_mqtt);
|
||
mqttConfigService.doSendView(jsonArray_mqtt,CommString.Mqtt_Topic_DATA);
|
||
}
|
||
|
||
// 生成时间点序列
|
||
List<String> timePoints = generateTimePoints(startTimeStr, dataCount, intervalMinutes);
|
||
|
||
// 处理模拟量数据 (type=4)
|
||
// System.out.println("\n电导率仪数据:");
|
||
Map<Integer, String> analogMappings = new HashMap<>();
|
||
analogMappings.put(7, "A7"); // 电导率值
|
||
analogMappings.put(8, "A8"); // 温度值
|
||
processSensorData(stationId, jsonArray, 4, analogMappings, "analogValues", timePoints);
|
||
}
|
||
|
||
// 雨量计处理逻辑
|
||
private void processRainGaugeData(JSONObject jsonObject, JSONArray jsonArray) {
|
||
// System.out.println("开始处理雨量计数据==============================================");
|
||
int dataCount = jsonObject.getIntValue("dataCount");
|
||
String startTimeStr = jsonObject.getString("startTime");
|
||
int intervalMinutes = jsonObject.getIntValue("intervalMinutes");
|
||
String stationId = jsonObject.getString("stationId");
|
||
|
||
// System.out.println("数据个数:" + dataCount);
|
||
// System.out.println("开始时间:" + startTimeStr);
|
||
// System.out.println("间隔分钟:" + intervalMinutes);
|
||
|
||
// 生成时间点序列
|
||
List<String> timePoints = generateTimePoints(startTimeStr, dataCount, intervalMinutes);
|
||
|
||
// 处理模拟量数据 (type=4)
|
||
// System.out.println("\n雨量计数据:");
|
||
Map<Integer, String> analogMappings = new HashMap<>();
|
||
analogMappings.put(1, "A1"); // 雨量值
|
||
processSensorData(stationId, jsonArray, 4, analogMappings, "analogValues", timePoints);
|
||
}
|
||
|
||
// 内涝监测处理逻辑
|
||
private void processWaterloggingMonitoringData(JSONObject jsonObject, JSONArray jsonArray) {
|
||
// System.out.println("开始处理内涝监测数据==============================================");
|
||
int dataCount = jsonObject.getIntValue("dataCount");
|
||
String startTimeStr = jsonObject.getString("startTime");
|
||
int intervalMinutes = jsonObject.getIntValue("intervalMinutes");
|
||
String stationId = jsonObject.getString("stationId");
|
||
|
||
// System.out.println("数据个数:" + dataCount);
|
||
// System.out.println("开始时间:" + startTimeStr);
|
||
// System.out.println("间隔分钟:" + intervalMinutes);
|
||
|
||
// 生成时间点序列
|
||
List<String> timePoints = generateTimePoints(startTimeStr, dataCount, intervalMinutes);
|
||
|
||
// 处理模拟量数据 (type=4)
|
||
// System.out.println("\n水位数据:");
|
||
Map<Integer, String> analogMappings = new HashMap<>();
|
||
analogMappings.put(13, "A13"); // 水位值(0x0D)
|
||
// analogMappings.put(14, "A14"); // 状态值(0x0D)
|
||
processSensorData(stationId, jsonArray, 4, analogMappings, "analogValues", timePoints);
|
||
|
||
// 处理脉冲量数据 (type=1)
|
||
// System.out.println("\n状态数据:");
|
||
Map<Integer, String> pulseMappings = new HashMap<>();
|
||
pulseMappings.put(14, "P14"); // 状态值(0x0E)
|
||
processSensorData(stationId, jsonArray, 1, pulseMappings, "accumulatedFlows", timePoints);
|
||
}
|
||
|
||
private DeviceData parseDeviceData(ByteBuf buf) {
|
||
// 查找起始符位置
|
||
int startIndex = -1;
|
||
for (int i = 0; i < buf.readableBytes() - 1; i++) {
|
||
if (buf.getByte(i) == (byte) 0xAB && buf.getByte(i + 1) == (byte) 0xCD) {
|
||
startIndex = i;
|
||
break;
|
||
}
|
||
}
|
||
if (startIndex == -1) {
|
||
// 没找到起始符,丢弃所有数据
|
||
buf.skipBytes(buf.readableBytes());
|
||
return null;
|
||
}
|
||
// 跳过起始符前的所有字节
|
||
buf.skipBytes(startIndex);
|
||
// 标记当前位置
|
||
buf.markReaderIndex();
|
||
// 验证起始符
|
||
byte b1 = buf.readByte();
|
||
byte b2 = buf.readByte();
|
||
if (b1 != (byte) 0xAB || b2 != (byte) 0xCD) {
|
||
buf.resetReaderIndex();
|
||
buf.skipBytes(1);
|
||
return null;
|
||
}
|
||
// 确保有足够数据读取长度
|
||
if (buf.readableBytes() < 2) {
|
||
buf.resetReaderIndex();
|
||
return null;
|
||
}
|
||
// 读取长度
|
||
int dataLength = buf.readUnsignedShort();
|
||
// 计算完整包长度
|
||
int totalLength = 2 + 2 + dataLength + 2; // AB CD + len + data + 结束符
|
||
// 确保有完整数据包
|
||
if (buf.readableBytes() < dataLength + 2) {
|
||
buf.resetReaderIndex();
|
||
return null;
|
||
}
|
||
// 读取完整数据包
|
||
ByteBuf frame = buf.readRetainedSlice(dataLength + 2);
|
||
DeviceData data = new DeviceData();
|
||
// 1. 基础信息解析
|
||
data.setStationId(frame.readUnsignedShort());
|
||
byte[] timeBytes = new byte[5];
|
||
frame.readBytes(timeBytes);
|
||
data.setStartTime(parseBcdTime(timeBytes));
|
||
data.setDataCount(frame.readUnsignedByte());
|
||
data.setIntervalMinutes(frame.readUnsignedByte());
|
||
data.setBatteryVoltage(frame.readUnsignedShort() / 100.0f);
|
||
data.setUploadCount(frame.readUnsignedShort());
|
||
//设备信息处理
|
||
int infoLength = frame.readUnsignedByte();
|
||
// 读取实际设备信息(固定20字节)
|
||
byte[] deviceInfo = new byte[20];
|
||
frame.readBytes(deviceInfo);
|
||
data.setDeviceInfo(new String(deviceInfo, StandardCharsets.US_ASCII).trim());
|
||
int bytesToSkip = 8; // 跳过预留的8字节
|
||
frame.skipBytes(bytesToSkip);
|
||
//解析传感器数据
|
||
List<SensorData> sensorDataList = new ArrayList<>();
|
||
while (frame.readableBytes() >= 2) {
|
||
int type = frame.readUnsignedByte();
|
||
int channel = frame.readUnsignedByte();
|
||
// System.out.printf("Parsing sensor: type=%02X, channel=%02X at pos=%d%n", type, channel, frame.readerIndex() - 2);
|
||
SensorData sensor = new SensorData();
|
||
sensor.setType(type);
|
||
sensor.setChannel(channel);
|
||
try {
|
||
switch (type) {
|
||
case 0x01: // 脉冲量
|
||
long yesterdayFlow = frame.readUnsignedIntLE();
|
||
sensor.setYesterdayFlow(yesterdayFlow);
|
||
List<Long> flows = new ArrayList<>();
|
||
for (int j = 0; j < data.getDataCount(); j++) {
|
||
if (frame.readableBytes() < 4) {
|
||
break;
|
||
}
|
||
flows.add(frame.readUnsignedIntLE());
|
||
}
|
||
sensor.setAccumulatedFlows(flows);
|
||
break;
|
||
|
||
case 0x02: // 开关量/报警量
|
||
// 每个通道只读取1字节状态值
|
||
if (frame.readableBytes() >= 1) {
|
||
int status = frame.readUnsignedByte();
|
||
List<Integer> statusList = new ArrayList<>();
|
||
statusList.add(status);
|
||
sensor.setDescription("开关量状态");
|
||
}
|
||
break;
|
||
case 0x41: // 不知道啥类型
|
||
// 不知道啥类型
|
||
if (frame.readableBytes() >= 1) {
|
||
|
||
}
|
||
break;
|
||
|
||
case 0x42: // 不知道啥类型
|
||
// 不知道啥类型
|
||
if (frame.readableBytes() >= 1) {
|
||
|
||
}
|
||
break;
|
||
|
||
case 0x43: // 不知道啥类型
|
||
// 不知道啥类型
|
||
if (frame.readableBytes() >= 1) {
|
||
|
||
}
|
||
break;
|
||
|
||
case 0x04: // 模拟量
|
||
List<Float> analogs = new ArrayList<>();
|
||
for (int j = 0; j < data.getDataCount(); j++) {
|
||
if (frame.readableBytes() < 4) {
|
||
break;
|
||
}
|
||
float value = 0.0f;
|
||
try {
|
||
// 智能井盖
|
||
if (String.valueOf(data.getStationId()).charAt(1) == '1') {
|
||
// 尝试不同的解析方式,根据通道类型选择合适的解析方法
|
||
switch (channel) {
|
||
case 1: // A1 信号强度 (dBm)
|
||
byte signalByte = frame.readByte();
|
||
frame.skipBytes(3); // 跳过剩余3字节
|
||
value = (float) signalByte;
|
||
break;
|
||
case 2: // A2 基站序号
|
||
value = (float) frame.readUnsignedIntLE();
|
||
break;
|
||
case 3: // A3 井盖角度
|
||
int angleInt = frame.readInt(); // 使用readInt()读取大端序
|
||
value = Float.intBitsToFloat(angleInt);
|
||
// 验证是否为合理的角度值
|
||
if (value < 0 || value > 360) {
|
||
frame.resetReaderIndex();
|
||
frame.skipBytes(j * 4);
|
||
int angleFixed = frame.readUnsignedShort();
|
||
frame.skipBytes(2);
|
||
value = (float) angleFixed / 100.0f;
|
||
}
|
||
break;
|
||
case 4: // A4 电阻值1
|
||
case 5: // A5 电阻值2
|
||
case 7: //
|
||
case 8: //
|
||
int resistanceInt1 = frame.readInt(); // 使用readInt()读取大端序
|
||
value = Float.intBitsToFloat(resistanceInt1);
|
||
break;
|
||
default: // 其他模拟量,默认使用浮点数解析
|
||
int intValue = frame.readIntLE();
|
||
value = Float.intBitsToFloat(intValue);
|
||
break;
|
||
}
|
||
}
|
||
// 流量计
|
||
if (String.valueOf(data.getStationId()).charAt(1) == '2') {
|
||
// 统一使用大端序读取IEEE754浮点数
|
||
int intValue = frame.readInt(); // 改为readInt()而不是readIntLE()
|
||
value = Float.intBitsToFloat(intValue);
|
||
// 保留3位小数
|
||
value = (float) (Math.round(value * 1000) / 1000.0);
|
||
// 格式化为3位小数,不足补零
|
||
String formattedValue = String.format("%.3f", value); // 例如 7.4 → "7.400"
|
||
value = Float.parseFloat(formattedValue); // 转回float(可选)
|
||
}
|
||
// 液位计
|
||
if (String.valueOf(data.getStationId()).charAt(1) == '3') {
|
||
// 统一使用大端序读取IEEE754浮点数
|
||
int intValue = frame.readInt(); // 改为readInt()而不是readIntLE()
|
||
value = Float.intBitsToFloat(intValue);
|
||
// 保留3位小数
|
||
value = (float) (Math.round(value * 1000) / 1000.0);
|
||
// 格式化为3位小数,不足补零
|
||
String formattedValue = String.format("%.3f", value); // 例如 7.4 → "7.400"
|
||
value = Float.parseFloat(formattedValue); // 转回float(可选)
|
||
}
|
||
// 电导率
|
||
/*if (String.valueOf(data.getStationId()).charAt(1) == '4') {
|
||
int intValue = frame.readIntLE();
|
||
value = Float.intBitsToFloat(intValue);
|
||
// 保留2位小数
|
||
value = (float) (Math.round(value * 100) / 100.0);
|
||
}*/
|
||
// 电导率
|
||
if (String.valueOf(data.getStationId()).charAt(1) == '4') {
|
||
// 改为使用大端序读取
|
||
int intValue = frame.readInt(); // 改为readInt()而不是readIntLE()
|
||
value = Float.intBitsToFloat(intValue);
|
||
// 保留2位小数
|
||
value = (float) (Math.round(value * 100) / 100.0);
|
||
}
|
||
// 雨量计
|
||
if (String.valueOf(data.getStationId()).charAt(1) == '5') {
|
||
// 改为使用大端序读取
|
||
int intValue = frame.readInt(); // 改为readInt()而不是readIntLE()
|
||
value = Float.intBitsToFloat(intValue);
|
||
// 保留2位小数
|
||
value = (float) (Math.round(value * 100) / 100.0);
|
||
}
|
||
// 内涝监测设备
|
||
if (String.valueOf(data.getStationId()).charAt(1) == '6') {
|
||
// 改为使用大端序读取
|
||
int intValue = frame.readInt(); // 改为readInt()而不是readIntLE()
|
||
value = Float.intBitsToFloat(intValue);
|
||
// 保留2位小数
|
||
value = (float) (Math.round(value * 100) / 100.0);
|
||
}
|
||
// 过滤异常值
|
||
if (Float.isNaN(value) || Float.isInfinite(value) || value < -1000000.0f || value > 1000000.0f) {
|
||
value = 0.0f;
|
||
}
|
||
} catch (Exception e) {
|
||
frame.skipBytes(4);
|
||
value = 0.0f;
|
||
}
|
||
analogs.add(value);
|
||
}
|
||
sensor.setAnalogValues(analogs);
|
||
// 智能井盖特定通道处理
|
||
if (String.valueOf(data.getStationId()).charAt(1) == '1') {
|
||
switch (channel) {
|
||
case 1: // A1信号强度
|
||
sensor.setDescription("A1信号强度");
|
||
break;
|
||
case 2: // A2基站序号
|
||
sensor.setDescription("A2基站序号");
|
||
break;
|
||
case 3: // A3井盖角度
|
||
sensor.setDescription("A3井盖角度");
|
||
break;
|
||
case 4: // A4电阻值1
|
||
sensor.setDescription("A4电阻值1");
|
||
break;
|
||
case 5: // A5电阻值2
|
||
sensor.setDescription("A5电阻值2");
|
||
break;
|
||
}
|
||
}
|
||
break;
|
||
case 0x05: // Q通道数据(数字量输出)
|
||
int qValue = frame.readUnsignedShort();
|
||
// 校验 qValue 范围(0x0000 ~ 0xFFFF)
|
||
if (qValue < 0 || qValue > 0xFFFF) {
|
||
throw new IllegalArgumentException("Invalid Q channel value: " + qValue);
|
||
}
|
||
|
||
// 解析各报警位状态
|
||
Map<AlarmType, Boolean> alarmStatus = new EnumMap<>(AlarmType.class);
|
||
for (AlarmType alarm : AlarmType.values()) {
|
||
alarmStatus.put(alarm, alarm.isTriggered(qValue));
|
||
}
|
||
|
||
// 构建报警描述
|
||
StringBuilder alarmDesc = new StringBuilder("Q通道状态: ");
|
||
for (AlarmType alarm : AlarmType.values()) {
|
||
alarmDesc.append(alarm.getDescription())
|
||
.append(alarmStatus.get(alarm) ? "触发" : "正常")
|
||
.append(", ");
|
||
}
|
||
sensor.setDescription(alarmDesc.toString().replaceAll(", $", ""));
|
||
|
||
// 存储原始报警值(可选)
|
||
sensor.setqValue(qValue);
|
||
sensor.setAlarmStatus(alarmStatus);
|
||
|
||
// 记录报警日志
|
||
if (alarmStatus.get(AlarmType.COVER_ALARM)) {
|
||
LoggerFactory.getLogger(UDPServer.class)
|
||
.warn("井盖开盖报警触发!站号: {}, Q值: {}", data.getStationId(), qValue);
|
||
}
|
||
break;
|
||
default:
|
||
int bytesToSkip2 = data.getDataCount() * 4;
|
||
if (frame.readableBytes() >= bytesToSkip2) {
|
||
frame.skipBytes(bytesToSkip2);
|
||
} else {
|
||
// System.err.printf("Not enough readable bytes to skip %d bytes. Current readable bytes: %d%n",
|
||
// bytesToSkip2, frame.readableBytes());
|
||
// 可选择抛出异常、记录日志或者做其他处理
|
||
}
|
||
continue;
|
||
}
|
||
sensorDataList.add(sensor);
|
||
} catch (Exception e) {
|
||
System.err.println("Error parsing sensor");
|
||
e.printStackTrace();
|
||
break;
|
||
}
|
||
// 检查是否到达结束符
|
||
if (frame.readableBytes() >= 2 && frame.getByte(frame.readerIndex()) == 0x0D && frame.getByte(frame.readerIndex() + 1) == 0x0A) {
|
||
// 跳过结束符
|
||
frame.skipBytes(2);
|
||
break;
|
||
}
|
||
}
|
||
data.setSensorDataList(sensorDataList);
|
||
return data;
|
||
}
|
||
|
||
private String parseBcdTime(byte[] bytes) {
|
||
// bytes: 19 05 29 10 47
|
||
int year = 2000 + ((bytes[0] >> 4) & 0xF) * 10 + (bytes[0] & 0xF);
|
||
int month = ((bytes[1] >> 4) & 0xF) * 10 + (bytes[1] & 0xF);
|
||
int day = ((bytes[2] >> 4) & 0xF) * 10 + (bytes[2] & 0xF);
|
||
int hour = ((bytes[3] >> 4) & 0xF) * 10 + (bytes[3] & 0xF);
|
||
int minute = ((bytes[4] >> 4) & 0xF) * 10 + (bytes[4] & 0xF);
|
||
|
||
return String.format("%04d-%02d-%02d %02d:%02d", year, month, day, hour, minute);
|
||
}
|
||
|
||
private static String bytesToHex(byte[] bytes) {
|
||
StringBuilder sb = new StringBuilder();
|
||
for (byte b : bytes) {
|
||
sb.append(String.format("%02X ", b));
|
||
}
|
||
return sb.toString();
|
||
}
|
||
|
||
|
||
/**
|
||
* 通用传感器数据处理方法
|
||
*
|
||
* @param stationId 站点id 用于redis组合key
|
||
* @param jsonArray 传感器数据数组
|
||
* @param type 要处理的数据类型
|
||
* @param channelMappings 通道映射
|
||
* @param valueFieldName 值字段名
|
||
* @param timePoints 时间点列表
|
||
*/
|
||
private void processSensorData(String stationId, JSONArray jsonArray, int type,
|
||
Map<Integer, String> channelMappings,
|
||
String valueFieldName,
|
||
List<String> timePoints) {
|
||
try {
|
||
for (Object obj : jsonArray) {
|
||
JSONObject sensorData = (JSONObject) obj;
|
||
if (sensorData.getIntValue("type") == type) {
|
||
int channel = sensorData.getIntValue("channel");
|
||
if (channelMappings.containsKey(channel)) {
|
||
String channelName = stationId + "_" + channelMappings.get(channel);
|
||
JSONArray values = sensorData.getJSONArray(valueFieldName);
|
||
|
||
if (values != null && !values.isEmpty()) {
|
||
//获取mpcode (优先从缓存拿)
|
||
String mpcode = getMpcodeFromCacheOrDB(stationId, channelName, channelMappings.get(channel));
|
||
if (!mpcode.equals("未配置")) {
|
||
JSONObject dataCache = new JSONObject();
|
||
|
||
JSONArray jsonArray_mqtt = new JSONArray();
|
||
for (int i = 0; i < values.size(); i++) {
|
||
String timePoint = timePoints.get(i);
|
||
timePoint = timePoint.length() == 16 ? timePoint + ":00" : timePoint;
|
||
Object value = values.get(i);
|
||
dataCache.put(timePoint, value);
|
||
//推送至kafka
|
||
mPointService.sendKafka4UDP("",mpcode, value, timePoint, i, values.size());
|
||
|
||
//添加到mqtt数组
|
||
JSONObject jsonObject_mqtt = new JSONObject();
|
||
jsonObject_mqtt.put("mpcode", mpcode);
|
||
jsonObject_mqtt.put("value", value);
|
||
jsonObject_mqtt.put("time", timePoint);
|
||
jsonArray_mqtt.add(jsonObject_mqtt);
|
||
}
|
||
|
||
//批量推送mqtt
|
||
mqttConfigService.doSendView(jsonArray_mqtt, CommString.Mqtt_Topic_DATA);
|
||
} else {
|
||
System.out.println("站点:" + stationId + " 标识:" + channelMappings.get(channel) + " 未配置");
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
} catch (JSONException e) {
|
||
e.printStackTrace();
|
||
}
|
||
}
|
||
|
||
// 从缓存或数据库获取mpcode
|
||
private String getMpcodeFromCacheOrDB(String stationId, String channelName, String tag) {
|
||
// 先查缓存
|
||
String mpcode = dataCache.get(channelName);
|
||
if (mpcode != null) {
|
||
return mpcode;
|
||
}
|
||
|
||
// 缓存没有则查询数据库
|
||
List<PipelineEquipment> equipmentList = pipelineEquipmentService.selectListByWhere(
|
||
"where station_code = '" + stationId + "'");
|
||
if (equipmentList != null && !equipmentList.isEmpty()) {
|
||
List<PipelineEquipmentMpoint> mpointList = pipelineEquipmentMpointService.selectListByWhere(
|
||
"where pid = '" + equipmentList.get(0).getId() + "' and tag = '" + tag + "'");
|
||
if (mpointList != null && !mpointList.isEmpty()) {
|
||
mpcode = mpointList.get(0).getMpcode();
|
||
// 存入缓存
|
||
dataCache.put(channelName, mpcode);
|
||
return mpcode;
|
||
}
|
||
}
|
||
return "未配置"; // 或根据业务需求返回默认值
|
||
}
|
||
|
||
//时间生成方法
|
||
private List<String> generateTimePoints(String startTimeStr, int count, int intervalMinutes) {
|
||
List<String> timePoints = new ArrayList<>();
|
||
try {
|
||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm");
|
||
Date date = sdf.parse(startTimeStr);
|
||
Calendar calendar = Calendar.getInstance();
|
||
calendar.setTime(date);
|
||
|
||
for (int i = 0; i < count; i++) {
|
||
timePoints.add(sdf.format(calendar.getTime()));
|
||
calendar.add(Calendar.MINUTE, intervalMinutes);
|
||
}
|
||
} catch (ParseException e) {
|
||
e.printStackTrace();
|
||
// 如果解析失败,使用简单的时间格式
|
||
for (int i = 0; i < count; i++) {
|
||
timePoints.add(startTimeStr + " +" + (i * intervalMinutes) + "分钟");
|
||
}
|
||
}
|
||
return timePoints;
|
||
}
|
||
} |