java中number & 0xff作用

本文详细介绍了如何使用按位与运算符 (&) 将一个整数转换为二进制形式,并仅保留最低的8位(bit)。通过具体的例子解释了0xff在位运算中的作用及其实现原理。

作用:把number转换为二进制,只取最低的8位(bit)。

解释:0xff二进制就是1111 1111。
& 运算含义:如果对应的两个bit都是1,则那个bit结果为1,否则为0.
比如 1010 & 1101 = 1000 (二进制)
由于0xff最低的8位是1,因此number中低8位中的&之后,如果原来是1,结果还是1,原来是0,结果位还是0.高于8位的,0xff都是0,所以无论是0还是1,结果都是0.
number如果为 0xabcd, 那么number & 0xff = number & 0x00ff = 0x00cd = 0xcd

import java.io.ByteArrayOutputStream; import java.net.*; import java.util.*; /** * UdpTsInspector * 监听 UDP 组播 TS,解析 PAT/PMT/SDT,节目号/节目名/各节目流速率统计 * JDK 1.6 兼容版 */ public class UdpTsInspector { // ==== 配置 ==== static final String MCAST_IP = "236.78.78.1"; static final int PORT = 59000; static final int MTU = 7 * 188; // UDP 一帧里常见多个 TS 包 // PSI 常量 static final int PID_PAT = 0x0000; static final int PID_SDT = 0x0011; // SDT/BAT static final int PID_NULL = 0x1FFF; // 节目与 PID 的关系 static final Map<Integer, Integer> progToPmtPid = new HashMap<Integer, Integer>(); // program_number -> PMT PID static final Map<Integer, Set<Integer>> programEsPids = new HashMap<Integer, Set<Integer>>(); // program_number -> ES PIDs static final Map<Integer, String> programNames = new HashMap<Integer, String>(); // program_number -> service_name // PID → 节目 的反查(便于统计) static final Map<Integer, Integer> pidToProgram = new HashMap<Integer, Integer>(); // PID 字节计数(用于码率) static final Map<Integer, Long> pidBytesCounter = new HashMap<Integer, Long>(); static long lastReportNs = System.nanoTime(); static final long REPORT_INTERVAL_NS = 3000000000L; // 3s // PSI 重组缓存:一个 PID 里可能会分段 static final Map<Integer, SectionAssembler> assemblers = new HashMap<Integer, SectionAssembler>(); // 每个节目:详细 ES 信息(pid + stream_type + 粗分类 + 友好名) static final Map<Integer, List<EsInfo>> programEsInfo = new HashMap<Integer, List<EsInfo>>(); static class EsInfo { int pid; int streamType; String kind; // "video" / "audio" / "other" String codec; // "H.264/AVC" / "HEVC" / "AAC" / "AC-3" ... EsInfo(int pid, int st, String kind, String codec) { this.pid = pid; this.streamType = st; this.kind = kind; this.codec = codec; } } public static void main(String[] args) throws Exception { System.out.println("Listen UDP multicast: " + MCAST_IP + ":" + PORT); MulticastSocket sock = new MulticastSocket(PORT); try { InetAddress grp = InetAddress.getByName(MCAST_IP); sock.joinGroup(grp); } catch (Throwable t) { System.err.println("Join group failed: " + t); } byte[] buf = new byte[MTU]; DatagramPacket pkt = new DatagramPacket(buf, buf.length); while (true) { sock.receive(pkt); int len = pkt.getLength(); int off = 0; while (off + 188 <= len) { parseTsPacket(buf, off); off += 188; } maybeReport(); } } /* 解析一个 188B TS 包 */ static void parseTsPacket(byte[] b, int off) { if ((b[off] & 0xFF) != 0x47) return; // sync int tei = (b[off+1] & 0x80) >>> 7; int pusi = (b[off+1] & 0x40) >>> 6; int pid = ((b[off+1] & 0x1F) << 8) | (b[off+2] & 0xFF); int afc = (b[off+3] & 0x30) >>> 4; if (tei == 1) return; // 传输错误,丢弃 // 统计:把 TS 包字节计入对应 PID(过滤 null) if (pid != PID_NULL) { Long old = pidBytesCounter.get(pid); pidBytesCounter.put(pid, (old == null ? 0L : old) + 188L); } int i = off + 4; if (afc == 2) return; // 只有 AF,无 payload if (afc == 3) { // 跳过 AF int afl = b[i] & 0xFF; i += 1 + afl; } if (i >= off + 188) return; // 仅处理 PSI(PAT/PMT/SDT)以获取节目/名称 if (pid == PID_PAT || pid == PID_SDT || containsValue(progToPmtPid, pid)) { SectionAssembler sa = assemblers.get(pid); if (sa == null) { sa = new SectionAssembler(); assemblers.put(pid, sa); } sa.push(b, i, off + 188 - i, pusi == 1, (b[off+3] & 0x0F)); // continuity 不做严格校验 while (sa.hasSection()) { byte[] sec = sa.pollSection(); parseSection(pid, sec); } } } // JDK6无Map.containsValue泛型重载,自己写 static boolean containsValue(Map<Integer, Integer> map, int val) { for (Integer v : map.values()) if (v != null && v.intValue() == val) return true; return false; } /* 解析一个 PSI section(含 table_id、section_length 等完整体) */ static void parseSection(int pid, byte[] sec) { if (sec.length < 3) return; int tableId = sec[0] & 0xFF; int sectionLength = ((sec[1] & 0x0F) << 8) | (sec[2] & 0xFF); if (sectionLength + 3 != sec.length) return; // 粗略校验 if (pid == PID_PAT && tableId == 0x00) { parsePAT(sec); } else if (pid == PID_SDT && (tableId == 0x42 || tableId == 0x46)) { parseSDT(sec); } else if (tableId == 0x02) { parsePMT(sec); } } static void parsePAT(byte[] sec) { // 解析PAT头部信息 int sectionSyntaxIndicator = (sec[1] & 0x80) >>> 7; int sectionLength = ((sec[1] & 0x0F) << 8) | (sec[2] & 0xFF); int transportStreamId = ((sec[3] & 0xFF) << 8) | (sec[4] & 0xFF); int versionNumber = (sec[5] & 0x3E) >>> 1; int currentNextIndicator = sec[5] & 0x01; int sectionNumber = sec[6] & 0xFF; int lastSectionNumber = sec[7] & 0xFF; System.out.printf("[PAT] TS ID: %d, Ver: %d, Sec: %d/%d, Length: %d%n", transportStreamId, versionNumber, sectionNumber, lastSectionNumber, sectionLength); int pos = 8; // 跳过0..7字段 int end = sec.length - 4; // 减去CRC32 // 计算节目数量 int programCount = (end - pos) / 4; System.out.printf("[PAT] 节目数量: %d%n", programCount); while (pos + 4 <= end) { int programNumber = ((sec[pos] & 0xFF) << 8) | (sec[pos+1] & 0xFF); int pmtPid = ((sec[pos+2] & 0x1F) << 8) | (sec[pos+3] & 0xFF); pos += 4; if (programNumber == 0) { // NIT PID System.out.printf("[PAT] NIT -> PID %d%n", pmtPid); pidToProgram.put(pmtPid, 0); // NIT的节目号为0 continue; } Integer old = progToPmtPid.put(programNumber, pmtPid); if (old == null || !old.equals(pmtPid)) { System.out.printf("[PAT] 节目 %d -> PMT PID %d%n", programNumber, pmtPid); pidToProgram.put(pmtPid, programNumber); } } // 打印CRC32校验码 int crc32 = ((sec[end] & 0xFF) << 24) | ((sec[end+1] & 0xFF) << 16) | ((sec[end+2] & 0xFF) << 8) | (sec[end+3] & 0xFF); System.out.printf("[PAT] CRC32: 0x%08X%n", crc32); } static void parsePMT(byte[] sec) { int tableId = sec[0] & 0xFF; int sectionSyntaxIndicator = (sec[1] & 0x80) >>> 7; int sectionLength = ((sec[1] & 0x0F) << 8) | (sec[2] & 0xFF); int programNumber = ((sec[3] & 0xFF) << 8) | (sec[4] & 0xFF); int versionNumber = (sec[5] & 0x3E) >>> 1; int currentNextIndicator = sec[5] & 0x01; int sectionNumber = sec[6] & 0xFF; int lastSectionNumber = sec[7] & 0xFF; int pcrPid = ((sec[8] & 0x1F) << 8) | (sec[9] & 0xFF); int progInfoLen = ((sec[10] & 0x0F) << 8) | (sec[11] & 0xFF); System.out.printf("[PMT] 节目 %d, PCR PID: %d, 节目信息长度: %d%n", programNumber, pcrPid, progInfoLen); int pos = 12 + progInfoLen; int end = sec.length - 4; // 旧:仅收集 ES PID;新:同时收集详细信息 Set<Integer> es = new HashSet<Integer>(); List<EsInfo> list = new ArrayList<EsInfo>(); while (pos + 5 <= end) { int streamType = sec[pos] & 0xFF; int elementaryPid = ((sec[pos+1] & 0x1F) << 8) | (sec[pos+2] & 0xFF); int esInfoLen = ((sec[pos+3] & 0x0F) << 8) | (sec[pos+4] & 0xFF); // 取 ES 描述符(可能用于识别 AC-3/E-AC-3/DTS) byte[] esDesc = null; if (pos + 5 + esInfoLen <= end && esInfoLen > 0) { esDesc = new byte[esInfoLen]; System.arraycopy(sec, pos + 5, esDesc, 0, esInfoLen); } String[] cls = classifyStream(streamType, esDesc); // 见③ list.add(new EsInfo(elementaryPid, streamType, cls[0], cls[1])); es.add(elementaryPid); pidToProgram.put(elementaryPid, programNumber); System.out.printf("[PMT] ES PID %d, 类型: 0x%02X (%s - %s), 描述符长度: %d%n", elementaryPid, streamType, cls[0], cls[1], esInfoLen); pos += 5 + esInfoLen; } programEsPids.put(programNumber, es); // 兼容你之前的用法 programEsInfo.put(programNumber, list); // 新增:带类型的清单 System.out.printf("[PMT] 节目 %d ES PIDs %s%n", programNumber, es); } static String[] classifyStream(int streamType, byte[] esDesc) { // 返回 [kind, codec];kind: video/audio/other switch (streamType & 0xFF) { case 0x01: return new String[]{"video", "MPEG-1 Video"}; case 0x02: return new String[]{"video", "MPEG-2 Video"}; case 0x10: return new String[]{"video", "MPEG-4 Video"}; case 0x1B: return new String[]{"video", "H.264/AVC"}; case 0x24: return new String[]{"video", "HEVC/H.265"}; case 0x03: return new String[]{"audio", "MPEG-1 Layer II"}; case 0x04: return new String[]{"audio", "MPEG-2 Audio"}; case 0x0F: return new String[]{"audio", "AAC"}; case 0x11: return new String[]{"audio", "LATM/LOAS AAC"}; case 0x06: // 0x06(私有数据)常见通过描述符判定 AC-3 / E-AC-3 / DTS if (hasDescriptor(esDesc, (byte)0x6A)) return new String[]{"audio", "AC-3"}; if (hasDescriptor(esDesc, (byte)0x7A)) return new String[]{"audio", "E-AC-3"}; if (hasDescriptor(esDesc, (byte)0x7B)) return new String[]{"audio", "DTS"}; return new String[]{"other", "Private data"}; default: return new String[]{"other", String.format("stream_type 0x%02X", streamType & 0xFF)}; } } static boolean hasDescriptor(byte[] esDesc, byte tag) { if (esDesc == null) return false; int i = 0, n = esDesc.length; while (i + 2 <= n) { int t = esDesc[i] & 0xFF; int l = esDesc[i+1] & 0xFF; if (i + 2 + l > n) break; if ((byte)t == tag) return true; i += 2 + l; } return false; } static void parseSDT(byte[] sec) { int tableId = sec[0] & 0xFF; int sectionSyntaxIndicator = (sec[1] & 0x80) >>> 7; int sectionLength = ((sec[1] & 0x0F) << 8) | (sec[2] & 0xFF); int transportStreamId = ((sec[3] & 0xFF) << 8) | (sec[4] & 0xFF); int versionNumber = (sec[5] & 0x3E) >>> 1; int currentNextIndicator = sec[5] & 0x01; int sectionNumber = sec[6] & 0xFF; int lastSectionNumber = sec[7] & 0xFF; int originalNetworkId = ((sec[8] & 0xFF) << 8) | (sec[9] & 0xFF); int reservedFutureUse = sec[10] & 0xFF; System.out.printf("[SDT] TS ID: %d, ON ID: %d, Ver: %d, Sec: %d/%d%n", transportStreamId, originalNetworkId, versionNumber, sectionNumber, lastSectionNumber); int pos = 11; int end = sec.length - 4; while (pos + 5 <= end) { int serviceId = ((sec[pos] & 0xFF) << 8) | (sec[pos+1] & 0xFF); int reservedFutureUse2 = (sec[pos+2] & 0xFC) >>> 2; int eitScheduleFlag = (sec[pos+2] & 0x02) >>> 1; int eitPresentFollowingFlag = sec[pos+2] & 0x01; int runningStatus = (sec[pos+3] & 0xE0) >>> 5; int freeCaMode = (sec[pos+3] & 0x10) >>> 4; int descriptorsLoopLen = ((sec[pos+3] & 0x0F) << 8) | (sec[pos+4] & 0xFF); int descPos = pos + 5; int descEnd = descPos + descriptorsLoopLen; String name = null; System.out.printf("[SDT] 服务 ID: %d, 运行状态: %d, CA模式: %d, 描述符长度: %d%n", serviceId, runningStatus, freeCaMode, descriptorsLoopLen); while (descPos + 2 <= descEnd && descEnd <= end) { int tag = sec[descPos] & 0xFF; int len = sec[descPos+1] & 0xFF; if (descPos + 2 + len > descEnd) break; if (tag == 0x48 && len >= 3) { // service_descriptor int serviceType = sec[descPos+2] & 0xFF; int serviceProviderNameLen = sec[descPos+3] & 0xFF; int nameLenPos = descPos + 4 + serviceProviderNameLen; if (nameLenPos + 1 <= descPos + 2 + len) { int serviceNameLen = sec[nameLenPos] & 0xFF; int nameStart = nameLenPos + 1; int nameEnd = Math.min(nameStart + serviceNameLen, descPos + 2 + len); // JDK6没有Arrays.copyOfRange byte[] nameBytes = new byte[nameEnd - nameStart]; System.arraycopy(sec, nameStart, nameBytes, 0, nameEnd - nameStart); name = decodeDvbString(nameBytes); } System.out.printf("[SDT] 服务类型: 0x%02X, 名称: %s%n", serviceType, name); } descPos += 2 + len; } if (name != null && name.length() > 0) { programNames.put(serviceId, name); System.out.printf("[SDT] 节目 %d 名称='%s'%n", serviceId, name); } pos = descEnd; } // 打印CRC32校验码 int crc32 = ((sec[end] & 0xFF) << 24) | ((sec[end+1] & 0xFF) << 16) | ((sec[end+2] & 0xFF) << 8) | (sec[end+3] & 0xFF); System.out.printf("[SDT] CRC32: 0x%08X%n", crc32); } static String decodeDvbString(byte[] bs) { if (bs == null || bs.length == 0) return ""; int offset = 0; String charset = "GB18030"; // 国内台普遍用 int first = bs[0] & 0xFF; if (first == 0x10 && bs.length >= 3) { charset = "UTF-16BE"; offset = 1; } else if (first == 0x15 || first == 0x14) { charset = "GB2312"; offset = 1; } else if (first == 0x1F) { charset = "UTF-8"; offset = 1; } else if (first < 0x20) { // 还有其它DVB编码,通常国内不用 offset = 1; } else { // 无编码标志,直接用GB18030尝试 offset = 0; } try { return new String(bs, offset, bs.length - offset, charset).trim(); } catch (Exception e) { try { return new String(bs, offset, bs.length - offset, "GB2312").trim(); } catch (Exception ignore) {} try { return new String(bs, offset, bs.length - offset, "UTF-8").trim(); } catch (Exception ignore) {} return ""; } } static void maybeReport() { long now = System.nanoTime(); if (now - lastReportNs < REPORT_INTERVAL_NS) return; long dtNs = now - lastReportNs; lastReportNs = now; Map<Integer, Long> programBytes = new HashMap<Integer, Long>(); for (Map.Entry<Integer, Long> e : pidBytesCounter.entrySet()) { int pid = e.getKey().intValue(); long bytes = e.getValue().longValue(); Integer prog = pidToProgram.get(pid); if (prog != null) { Long old = programBytes.get(prog); programBytes.put(prog, (old == null ? 0L : old) + bytes); } } pidBytesCounter.clear(); if (programBytes.isEmpty()) return; System.out.println("----- 3s 统计 -----"); for (Map.Entry<Integer, Long> e : programBytes.entrySet()) { int program = e.getKey().intValue(); long bytes = e.getValue().longValue(); double mbps = (bytes * 8.0) / (dtNs / 1e9) / 1_000_000.0; String name = programNames.get(program); if (name == null) name = "(解析中…)"; // PMT PID 来自 PAT Integer pmtPid = progToPmtPid.get(program); // 选第一个视频、前两个音频 EsInfo v = null, a1 = null, a2 = null; List<EsInfo> list = programEsInfo.get(program); if (list != null) { for (int i = 0; i < list.size(); i++) { EsInfo es = list.get(i); if ("video".equals(es.kind) && v == null) { v = es; } else if ("audio".equals(es.kind)) { if (a1 == null) a1 = es; else if (a2 == null) a2 = es; } } } System.out.printf( "Program %d %-16s Rate: %.3f Mbit/s PMT:%s V:%s(%s) A1:%s(%s) A2:%s(%s)%n", program, name, mbps, (pmtPid == null ? "-" : String.valueOf(pmtPid)), (v==null ? "-" : String.valueOf(v.pid)), (v==null ? "-" : v.codec), (a1==null? "-" : String.valueOf(a1.pid)), (a1==null? "-" : a1.codec), (a2==null? "-" : String.valueOf(a2.pid)), (a2==null? "-" : a2.codec) ); } } // ===== PSI Section 重组器 ===== static class SectionAssembler { private final ByteArrayOutputStream cur = new ByteArrayOutputStream(); private Integer expectedLen = null; private final Queue<byte[]> ready = new LinkedList<byte[]>(); void push(byte[] src, int off, int len, boolean payloadStart, int continuityCounter) { int i = off; if (payloadStart) { int pointer = src[i] & 0xFF; i += 1; while (pointer > 0 && i < off + len) { i++; pointer--; } startNew(); } while (i < off + len) { if (expectedLen == null) { if (off + len - i < 3) { // 不够section头 write(src, i, off + len - i); return; } int sl = ((src[i+1] & 0x0F) << 8) | (src[i+2] & 0xFF); expectedLen = sl + 3; } int remaining = expectedLen.intValue() - cur.size(); int copy = Math.min(remaining, off + len - i); write(src, i, copy); i += copy; if (cur.size() == expectedLen.intValue()) { ready.add(cur.toByteArray()); startNew(); } } } boolean hasSection() { return !ready.isEmpty(); } byte[] pollSection() { return ready.poll(); } private void startNew() { cur.reset(); expectedLen = null; } private void write(byte[] src, int off, int len) { cur.write(src, off, len); } } } 帮我修改代码 解析 打印PAT表主要内容
最新发布
09-19
package demo1; import javax.swing.*; public class Main { public static void main(String[] args) { SwingUtilities.invokeLater(() -> { TemperatureMonitorGUI gui = new TemperatureMonitorGUI(); gui.setVisible(true); }); } } package demo1; import com.fazecast.jSerialComm.SerialPort; import com.fazecast.jSerialComm.SerialPortDataListener; import com.fazecast.jSerialComm.SerialPortEvent; public class SerialPortDataListenerImpl implements SerialPortDataListener { private final TemperatureMonitorGUI gui; private final TemperatureController controller; private final SerialPort serialPort; private final byte[] buffer = new byte[256]; private int bufferIndex = 0; public SerialPortDataListenerImpl(SerialPort serialPort, TemperatureMonitorGUI gui, TemperatureController controller) { this.serialPort = serialPort; this.gui = gui; this.controller = controller; } @Override public int getListeningEvents() { return SerialPort.LISTENING_EVENT_DATA_AVAILABLE; } @Override public void serialEvent(SerialPortEvent event) { if (event.getEventType() == SerialPort.LISTENING_EVENT_DATA_AVAILABLE) { byte[] newData = new byte[serialPort.bytesAvailable()]; int numRead = serialPort.readBytes(newData, newData.length); // 将新数据添加到缓冲区 System.arraycopy(newData, 0, buffer, bufferIndex, numRead); bufferIndex += numRead; // 处理缓冲区中的数据 processBuffer(); } } private void processBuffer() { // 检查是否有完整帧(最小帧长为5字节) if (bufferIndex < 5) return; // 检查帧头(无特定帧头,需检查功能码) int functionCode = buffer[1] & 0xFF; // 处理错误响应(功能码最高位为1) if ((functionCode & 0x80) != 0) { if (bufferIndex >= 5) { // 错误响应帧长5字节 handleErrorResponse(); removeProcessedBytes(5); } return; } // 根据功能码处理响应 switch (functionCode) { case 0x03: // 读寄存器 handleReadResponse(); break; case 0x06: // 写单个寄存器 handleWriteResponse(); break; case 0x08: // 回路侦测 handleLoopbackResponse(); break; default: // 未知功能码,丢弃该字节 removeProcessedBytes(1); } } private void handleReadResponse() { // 读响应帧结构:[地址][功能码][字节数][数据...][CRC低][CRC高] int byteCount = buffer[2] & 0xFF; int frameLength = 3 + byteCount + 2; // 地址+功能码+字节数+数据+CRC if (bufferIndex >= frameLength) { // 验证CRC if (!verifyCRC(buffer, frameLength)) { System.err.println("CRC校验失败"); removeProcessedBytes(frameLength); return; } // 解析寄存器地址 int registerAddr = ((buffer[2] & 0xFF) << 8) | (buffer[3] & 0xFF); // 根据寄存器地址处理数据 switch (registerAddr) { case 0x0000: // 温度测量值 int tempValue = ((buffer[4] & 0xFF) << 8) | (buffer[5] & 0xFF); double temperature = tempValue / 10.0; controller.updateTemperature(temperature); break; case 0x0001: // 湿度测量值 int humidity = ((buffer[4] & 0xFF) << 8) | (buffer[5] & 0xFF); // 处理湿度值... break; case 0x00F8: // 连续读取 handleContinuousReadResponse(); break; default: // 其他寄存器处理... } removeProcessedBytes(frameLength); } } private void handleContinuousReadResponse() { int byteCount = buffer[2] & 0xFF; int frameLength = 3 + byteCount + 2; if (bufferIndex >= frameLength) { // 验证CRC if (!verifyCRC(buffer, frameLength)) { System.err.println("连续读取CRC校验失败"); removeProcessedBytes(frameLength); return; } // 解析连续数据 int index = 3; // 数据起始位置 // 运行时间(2字节) int runTime = ((buffer[index++] & 0xFF) << 8) | (buffer[index++] & 0xFF); // 设定时间(2字节) int setTime = ((buffer[index++] & 0xFF) << 8) | (buffer[index++] & 0xFF); // 测量温度(2字节,单位0.1°C) int tempMeasured = ((buffer[index++] & 0xFF) << 8) | (buffer[index++] & 0xFF); double temperature = tempMeasured / 10.0; controller.updateTemperature(temperature); // 设定温度(2字节) int tempSet = ((buffer[index++] & 0xFF) << 8) | (buffer[index++] & 0xFF); // 测量湿度(2字节) int humidityMeasured = ((buffer[index++] & 0xFF) << 8) | (buffer[index++] & 0xFF); // 设定湿度(2字节) int humiditySet = ((buffer[index++] & 0xFF) << 8) | (buffer[index++] & 0xFF); // 光照级数(4字节) int lightGroup2 = buffer[index++] & 0xFF; int lightGroup1 = buffer[index++] & 0xFF; int lightGroup3 = buffer[index++] & 0xFF; int lightGroup1Repeat = buffer[index++] & 0xFF; // 可能是重复或备用 // 转速(4字节) int rpmMeasured = ((buffer[index++] & 0xFF) << 8) | (buffer[index++] & 0xFF); int rpmSet = ((buffer[index++] & 0xFF) << 8) | (buffer[index++] & 0xFF); // CO2浓度(4字节) int co2Measured = ((buffer[index++] & 0xFF) << 8) | (buffer[index++] & 0xFF); int co2Set = ((buffer[index++] & 0xFF) << 8) | (buffer[index++] & 0xFF); // O2浓度(4字节) int o2Measured = ((buffer[index++] & 0xFF) << 8) | (buffer[index++] & 0xFF); int o2Set = ((buffer[index++] & 0xFF) << 8) | (buffer[index++] & 0xFF); // 压力(4字节) int pressureMeasured = ((buffer[index++] & 0xFF) << 8) | (buffer[index++] & 0xFF); int pressureSet = ((buffer[index++] & 0xFF) << 8) | (buffer[index++] & 0xFF); // 备份数据(8字节) // ... removeProcessedBytes(frameLength); } } private void handleWriteResponse() { // 写响应帧结构:[地址][功能码][寄存器地址高][寄存器地址低][值高][值低][CRC低][CRC高] int frameLength = 8; if (bufferIndex >= frameLength) { // 验证CRC if (!verifyCRC(buffer, frameLength)) { System.err.println("写响应CRC校验失败"); removeProcessedBytes(frameLength); return; } int registerAddr = ((buffer[2] & 0xFF) << 8) | (buffer[3] & 0xFF); int value = ((buffer[4] & 0xFF) << 8) | (buffer[5] & 0xFF); System.out.println("寄存器写入成功: 地址=" + Integer.toHexString(registerAddr) + ", 值=" + Integer.toHexString(value)); gui.setStatus("寄存器写入成功"); removeProcessedBytes(frameLength); } } private void handleLoopbackResponse() { // 回路侦测响应帧结构:[地址][功能码][子功能高][子功能低][数据高][数据低][CRC低][CRC高] int frameLength = 8; if (bufferIndex >= frameLength) { // 验证CRC if (!verifyCRC(buffer, frameLength)) { System.err.println("回路侦测CRC校验失败"); removeProcessedBytes(frameLength); return; } int subFunction = ((buffer[2] & 0xFF) << 8) | (buffer[3] & 0xFF); int data = ((buffer[4] & 0xFF) << 8) | (buffer[5] & 0xFF); System.out.println("回路侦测成功: 子功能=" + Integer.toHexString(subFunction) + ", 数据=" + Integer.toHexString(data)); gui.setStatus("回路侦测成功"); removeProcessedBytes(frameLength); } } private void handleErrorResponse() { int slaveId = buffer[0] & 0xFF; int functionCode = buffer[1] & 0xFF; int errorCode = buffer[2] & 0xFF; String errorMessage = "错误响应: "; switch (errorCode) { case 0x01: errorMessage += "功能码错误"; break; case 0x02: errorMessage += "寄存器地址错误"; break; case 0x03: errorMessage += "资料内容值错误"; break; case 0x04: errorMessage += "控制器无法处理"; break; case 0x09: errorMessage += "CRC校验错误"; break; case 0x0A: errorMessage += "奇偶校验错误"; break; case 0x0C: errorMessage += "接收数据低于规定长度"; break; case 0x0D: errorMessage += "接收数据超过规定长度"; break; default: errorMessage += "未知错误码: " + errorCode; } System.err.println(errorMessage + " (地址码: " + slaveId + ", 功能码: " + (functionCode & 0x7F) + ")"); gui.setStatus(errorMessage); } private boolean verifyCRC(byte[] data, int length) { // 提取数据和CRC byte[] frameData = new byte[length - 2]; System.arraycopy(data, 0, frameData, 0, length - 2); byte[] receivedCRC = new byte[]{data[length - 2], data[length - 1]}; byte[] calculatedCRC = controller.calculateCRC(frameData); return (receivedCRC[0] == calculatedCRC[0] && receivedCRC[1] == calculatedCRC[1]); } private void removeProcessedBytes(int count) { if (count > bufferIndex) { count = bufferIndex; } // 将剩余数据前移 System.arraycopy(buffer, count, buffer, 0, bufferIndex - count); bufferIndex -= count; } } package demo1; import com.fazecast.jSerialComm.SerialPort; public class TemperatureController { private final TemperatureMonitorGUI gui; private final String portName; private SerialPort serialPort; private boolean running = false; public TemperatureController(TemperatureMonitorGUI gui, String portName) { this.gui = gui; this.portName = portName; } public void start() { new Thread(this::connectSerialPort).start(); } public void stop() { running = false; if (serialPort != null && serialPort.isOpen()) { serialPort.closePort(); } } private void connectSerialPort() { serialPort = SerialPort.getCommPort(portName); System.out.println("尝试连接串口: " + portName); // 设置串口参数(MODBUS RTU 标准参数) // 9600/8/N/1 serialPort.setComPortParameters(9600, 8, SerialPort.ONE_STOP_BIT, SerialPort.NO_PARITY); // 4800/8/N/2 // serialPort.setComPortParameters(4800, 8, SerialPort.TWO_STOP_BITS, SerialPort.NO_PARITY); serialPort.setComPortTimeouts(SerialPort.TIMEOUT_READ_SEMI_BLOCKING, 1000, 0); if (serialPort.openPort()) { System.out.println("✅ 成功打开串口"); gui.setStatus("已连接到 " + portName); serialPort.addDataListener(new SerialPortDataListenerImpl(serialPort, gui, this)); // 启动温度轮询 startTemperaturePolling(); } else { System.err.println("❌ 无法打开串口"); gui.setStatus("无法打开串口:" + portName); } } private void startTemperaturePolling() { new Thread(() -> { while (!Thread.interrupted()) { try { // 读取温度测量值(寄存器地址0x0000) readRegister((byte) 0x01, 0x0000); Thread.sleep(1000); // 每秒读取一次 } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } }).start(); } // 读取寄存器(功能码03H) public void readRegister(byte slaveId, int registerAddr) { // 构造请求帧 byte[] request = new byte[6]; request[0] = slaveId; // 地址码 request[1] = 0x03; // 功能码(读寄存器) request[2] = (byte) (registerAddr >> 8); // 寄存器地址高字节 request[3] = (byte) registerAddr; // 寄存器地址低字节 request[4] = 0x00; // 寄存器数量高字节 request[5] = 0x01; // 寄存器数量低字节(读取1个寄存器) // 计算CRC并发送 sendRequest(request); } // 写入寄存器(功能码06H) public void writeRegister(byte slaveId, int registerAddr, int value) { // 构造请求帧 byte[] request = new byte[6]; request[0] = slaveId; // 地址码 request[1] = 0x06; // 功能码(写寄存器) request[2] = (byte) (registerAddr >> 8); // 寄存器地址高字节 request[3] = (byte) registerAddr; // 寄存器地址低字节 request[4] = (byte) (value >> 8); // 值高字节 request[5] = (byte) value; // 值低字节 // 计算CRC并发送 sendRequest(request); } // 回路侦测(功能码08H) public void loopbackTest(byte slaveId) { // 构造请求帧 byte[] request = new byte[6]; request[0] = slaveId; // 地址码 request[1] = 0x08; // 功能码(回路侦测) request[2] = 0x00; // 子功能码高字节(任意) request[3] = 0x00; // 子功能码低字节(任意) request[4] = 0x12; // 数据高字节(任意) request[5] = 0x34; // 数据低字节(任意) // 计算CRC并发送 sendRequest(request); } // 连续读取(功能码03H,特殊地址0x00F8) public void continuousRead(byte slaveId) { // 构造请求帧 byte[] request = new byte[6]; request[0] = slaveId; // 地址码 request[1] = 0x03; // 功能码(读寄存器) request[2] = 0x00; // 寄存器地址高字节 (0x00F8) request[3] = (byte) 0xF8; // 寄存器地址低字节 request[4] = 0x00; // 寄存器数量高字节 request[5] = 0x14; // 寄存器数量低字节(读取20个寄存器) // 计算CRC并发送 sendRequest(request); } // 发送请求(添加CRC校验) private void sendRequest(byte[] data) { if (serialPort == null || !serialPort.isOpen()) { System.err.println("串口未打开,无法发送数据"); return; } // 计算CRC byte[] crc = calculateCRC(data); // 创建完整请求帧 byte[] fullRequest = new byte[data.length + 2]; System.arraycopy(data, 0, fullRequest, 0, data.length); fullRequest[data.length] = crc[0]; // CRC低字节 fullRequest[data.length + 1] = crc[1]; // CRC高字节 // 发送数据 serialPort.writeBytes(fullRequest, fullRequest.length); System.out.println("发送请求: " + bytesToHex(fullRequest)); } // CRC16计算(MODBUS标准) public byte[] calculateCRC(byte[] data) { int crc = 0xFFFF; for (byte b : data) { crc ^= (b & 0xFF); for (int i = 0; i < 8; i++) { if ((crc & 0x0001) != 0) { crc >>= 1; crc ^= 0xA001; } else { crc >>= 1; } } } return new byte[]{(byte) crc, (byte) (crc >> 8)}; } // 字节数组转十六进制字符串(调试用) private String bytesToHex(byte[] bytes) { StringBuilder sb = new StringBuilder(); for (byte b : bytes) { sb.append(String.format("%02X ", b)); } return sb.toString().trim(); } public void updateTemperature(double temperature) { gui.updateTemperature(temperature); } } package demo1; import javax.swing.; import java.awt.; import java.awt.event.ActionEvent; public class TemperatureMonitorGUI extends JFrame { private JLabel temperatureLabel; private JLabel statusLabel; private JButton connectButton; private JButton readButton; private JButton writeButton; private JButton loopbackButton; private JComboBox portComboBox; private TemperatureController controller; // 输入组件 private JTextField slaveIdField; private JTextField registerAddrField; private JTextField valueField; private JComboBox<String> functionCodeCombo; public TemperatureMonitorGUI() { setTitle("MODBUS RTU 温度监控系统"); setSize(600, 500); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setLayout(new GridLayout(8, 2, 5, 5)); // 串口选择 add(new JLabel("串口:")); portComboBox = new JComboBox<>(); refreshPortList(); add(portComboBox); // 从站ID add(new JLabel("地址码 (HEX):")); slaveIdField = new JTextField("01"); add(slaveIdField); // 功能码选择 add(new JLabel("功能码:")); functionCodeCombo = new JComboBox<>(new String[]{"03H: 读寄存器", "06H: 写寄存器", "08H: 回路侦测"}); add(functionCodeCombo); // 寄存器地址 add(new JLabel("寄存器地址 (HEX):")); registerAddrField = new JTextField("0000"); add(registerAddrField); // 值 add(new JLabel("值 (HEX):")); valueField = new JTextField("0000"); add(valueField); // 温度显示 temperatureLabel = new JLabel("当前温度: --°C", SwingConstants.CENTER); temperatureLabel.setFont(new Font("Arial", Font.BOLD, 24)); add(temperatureLabel); // 状态栏 statusLabel = new JLabel("状态: 未连接", SwingConstants.CENTER); add(statusLabel); // 控制按钮 JPanel buttonPanel = new JPanel(new GridLayout(1, 4, 5, 5)); connectButton = new JButton("连接串口"); readButton = new JButton("读取"); writeButton = new JButton("写入"); loopbackButton = new JButton("回路测试"); buttonPanel.add(connectButton); buttonPanel.add(readButton); buttonPanel.add(writeButton); buttonPanel.add(loopbackButton); add(buttonPanel); // 初始化控制器 controller = null; connectButton.addActionListener((ActionEvent e) -> { String selectedPort = (String) portComboBox.getSelectedItem(); if (selectedPort != null) { controller = new TemperatureController(this, selectedPort); controller.start(); } }); readButton.addActionListener(e -> { if (controller != null) { try { int slaveId = Integer.parseInt(slaveIdField.getText(), 16); int registerAddr = Integer.parseInt(registerAddrField.getText(), 16); controller.readRegister((byte) slaveId, registerAddr); } catch (NumberFormatException ex) { JOptionPane.showMessageDialog(this, "请输入有效的十六进制数字!"); } } }); writeButton.addActionListener(e -> { if (controller != null) { try { int slaveId = Integer.parseInt(slaveIdField.getText(), 16); int registerAddr = Integer.parseInt(registerAddrField.getText(), 16); int value = Integer.parseInt(valueField.getText(), 16); controller.writeRegister((byte) slaveId, registerAddr, value); } catch (NumberFormatException ex) { JOptionPane.showMessageDialog(this, "请输入有效的十六进制数字!"); } } }); loopbackButton.addActionListener(e -> { if (controller != null) { try { int slaveId = Integer.parseInt(slaveIdField.getText(), 16); controller.loopbackTest((byte) slaveId); } catch (NumberFormatException ex) { JOptionPane.showMessageDialog(this, "请输入有效的十六进制数字!"); } } }); } public void refreshPortList() { portComboBox.removeAllItems(); for (com.fazecast.jSerialComm.SerialPort port : com.fazecast.jSerialComm.SerialPort.getCommPorts()) { portComboBox.addItem(port.getSystemPortName()); } } public void updateTemperature(double temperature) { temperatureLabel.setText(String.format("当前温度: %.1f°C", temperature)); } public void setStatus(String status) { statusLabel.setText("状态: " + status); } public static void main(String[] args) { SwingUtilities.invokeLater(() -> { new TemperatureMonitorGUI().setVisible(true); }); } } MODBUS_RTU通讯协议(客户) 1.字元结构 1.1 10―bit字元框(FOR ASCII) 资料格式 8. N .1 START BIT 0 1 2 3 4 5 6 7 STOP BIT 8-data bits 10-bits character fram 1.2 11―bit字元框(FOR RTU) 资料格式 8. N .2 START BIT 0 1 2 3 4 5 6 7 STOP BIT STOP BIT 8-data bits 11-bits character fram 资料格式 8. E .1 START BIT 0 1 2 3 4 5 6 7 Even Parity STOP BIT 8-data bits 11-bits character fram 资料格式 8. O. 1 START BIT 0 1 2 3 4 5 6 7 Odd Parity STOP BIT 8-data bits 11-bits character fram 波特率:1200,2400,4800,9600,19200 2.通信资料格式 RTU模式 START 保持无输入讯号≧ 20ms Adress 通信位址:8-bit 二进制位址。00H为广播地址,使用广播地址时只能接一台控制器。 Function 功能码:8-bit 二进制位址 DATA(n-1) 资料内容: n8-bit 资料内容 …… DATA 0 CRC CHK Low CRC 检查码: 由2个8-bit二进制码组成 CRC CHK High END Hi 保持无输入讯号≧20ms 2.3功能码: 03H:读出暂存器内容 06H:写入一个WORD至寄存器 08H:回路侦测 2.3.1功能码08H:回路侦测。 RTU 模式:询问格式: 回应格式: Address 01H Address 01H Function 08H Function 08H Sub-Func-Hi 00H (任意) Sub-Func-Hi 00H Sub-Func-Lo 00H (任意) Sub-Func-Lo 00H Data Content 12H (任意) Data Content 12H 34H (任意) 34H CRC Lo EDH CRC Lo EDH CRC Hi 7CH CRC Hi 7CH 2.3.2功能码03H:读出暂存器内容。 例如:从起始暂存器(位址0000)读出2个连续资料内容,假设寄存器(0000)=0100H,(0001)=00F0H。 RTU模式:询问格式: 回应格式 Address 01H Address 01H Function 03H Function 03H Data Addr 00H Number of data (count by byte) 04H 00H Number of data (count by word) 00H Content of data (Address 000001H 02H 00H CRC Low C4H Content of data (Address 0001) 00H CRC Hight 0BH F0H CRC CHK Low FBH CRC CHK Hight 8BH 2.3.3功能码06H:写入一个WORD至暂存器。 例如:对驱动器位址01H,写入03E8H到参数0010H。 询问格式: 回应格式: Address 01H Address 01H Function 06H Function 06H Data Addr 00H Data Addr 00H 10H 10H Data Content 03H Data Content 03H E8H E8H CRC Low 88H CRC CHK Low 88H CRC Hight B1H CRC CHK Hight B1H 2.4.错误通讯时的额外回应: 当控制器做通信连接时,如果产生错误,此时控制器会回应错误码且将Function code AND 80H回应给主控系统,让主控系统知道有错误产生。参考错误通信时错误码的意义 RTU模式: Address 01H Function 86H Except code 02H CRC CHR Low C3H CRC CHR Hight A1H 错误码的意义: 错误码 说明 01 功能码错误; 控制器可以辨识的功能码为03H,06H,08H 02 寄存器地址错误; 资料的位址控制器无法辨识 03 资料内容值错误 资料内容值太大或者太小,不是控制器所能辨识的内容值 04 控制器无法处理; 控制器对此命令,无法执行 09 CRC或者LRC校验错误 10 奇偶校验错误 12 接收数据低于规定长度 13 接收数据超过规定长度 其中将原功能号AND 80H后返回。并在Except code中返回错误码(见右上表格) 2.5 RTU模式的检查码(CRC Check) 检查码由Address到Data content结束。其运算规则如下: 步骤1:令16-bit暂存器(CRC暂存器)=FFFFH。 步骤2:Exclusive OR第一个8-bite byte的讯息指令与低位元16-bite CRC暂存器,做Exclusive OR,将结果存入CRC暂存器内。 步骤3:右移位CRC暂存器,将0填入高位元处。 步骤4:检查右移的值,如果是0,将步骤3的新值存入CRC暂存器内,否则Exclusive OR A001H与CRC暂存器,将结果存入CRC暂存器内。 步骤5:重复步骤3~步骤4,将8-bit全部运算完成。 步骤6:重复步骤2~步骤5,取下一个8-bit的讯息指令,直到所有讯息指令运算完成。最后,得到的CRC暂存器的值,即是CRC的检查码。值得注意的是CRC的检查码必须交换放置於讯息指令的检查码中。 以下为用c语言所写的crc检查码运算范例: unsigned char *data; unsigned char length; unsigned int crc_chk(unsigned char *data,unsigned char length) { int j;unsigned int reg_crc=0xffff; while(length--){ reg_crc^=*data++; for(j=0;j<8;j++){ if(reg_crc&0x01){ reg_crc=(reg_crc>>1)^0xa001;} else{ reg_crc=reg_crc>>1; } } } return reg_crc; } 3:通讯参数修改:同时按住SET键加上移位键4秒以上,出现LK代码,按数字键使LK数值为118,再:按SET键进入通讯参数设置(SET键即功能键)。 意义 参数名 说 明 通讯地址 Ad 0-32 0:广播地址 通讯波特率 bA 2:4800 3:9600 4:19200 通讯格式 Fo 3:MODBUS-RTU(8,N,2) 4:MODBUS-RTU(8,E,1) 5:MODBUS-RTU(8,O,1)6: MODBUS-RTU(8,N,1) 4.参数位址定义: 0x0000: 温度测量值 只能读 0x0001: 湿度测量值 只能读 0x0002: 读:剩余运行时间。 写:写入0,关闭手动输出;写入1,打开手动输出。比如照明,循环。 0x0003/0x0004:温度设定值 可读可写。 0x0006: 定时设定值 可读可写。 0x000a: 读:.0 超过允许最高温度报警 .1温度上偏差报警 .2温度传感器故障 .6 温度下偏差报警 .15 总报警 写:写入1 在鸣叫和静音之间切换。 0x000b: 读:0x000b.11=1运行 =0停止 写:写入1,启动或停止运行 读:0x000b.8=1 手动输出打开 =0 手动输出关闭。 0x000d: 转速测量值 只读 0x0012: CO2浓度测量值 只读 0x0013: 压力测量值 只读 0x0018: 转速设定值 可读可写 0x0019: 湿度设定值 可读可写 0x001a: 第一组光照设定值 可读可写 0x001b: CO2设定值 可读可写 0x001c: O2设定值 可读可写 0x001d: 压力设定值 可读可写 0x001e: 第二组光照设定值 可读可写 0x001f: 第三组光照设定值 可读可写 0x00e8 O2氧浓度测量值 只读 0x00f8: 连读 上位机发送:通讯地址+03H+00H+F8H+00H+14H+CRC(L)+CRC(H)。此处0x00F8必须优先。 控制器返回:通讯地址+03H+28H+运行时间高位+运行时间低位+设定时间高位+设定时间低位+测量温度高位+测量温度低位+设定温度高位+设定温度低位+测量湿度高位+测量湿度低位+设定湿度高位+设定湿度低位+第二组光照级数+第一组光照级数+第三组光照级数+第一组光照级数+测量转速高位+测量转速低位+设定转速高位+设定转速低位+测量CO2浓度高位+测量CO2浓度低位+设定CO2浓度高位+设定CO2浓度低位+测量O2浓度高位+测量O2浓度低位+设定O2浓度高位+设定O2浓度低位+测量压力高位+测量压力低位+设定压力高位+设定压力低位+备份1高位+备份1低位+备份2高位+备份2低位+备份3高位+备份3低位+备份4高位+备份4低位+ CRC(L)+CRC(H). 按照这个协议,补全功能,原来的功能不能消失,只能加功能,不能减,之后将完整代码发我
07-15
package demo1; import javax.swing.*; public class Main { public static void main(String[] args) { SwingUtilities.invokeLater(() -> { TemperatureMonitorGUI gui = new TemperatureMonitorGUI(); gui.setVisible(true); }); } } package demo1; public class ModbusRequestBuilder { public static byte[] buildReadRequest(byte slaveId, int registerAddr) { return buildRequest(slaveId, 0x03, registerAddr, 0x0001); } public static byte[] buildWriteRequest(byte slaveId, int registerAddr, int value) { return new byte[]{ slaveId, 0x06, (byte) (registerAddr >> 8), (byte) registerAddr, (byte) (value >> 8), (byte) value }; } private static byte[] buildRequest(byte slaveId, int functionCode, int registerAddr, int wordCount) { return new byte[]{ slaveId, (byte) functionCode, (byte) (registerAddr >> 8), (byte) registerAddr, (byte) (wordCount >> 8), (byte) wordCount }; } public static byte[] calculateCRC(byte[] data) { int crc = 0xFFFF; for (byte b : data) { crc ^= (b & 0xFF); for (int i = 0; i < 8; i++) { if ((crc & 0x0001) != 0) { crc >>= 1; crc ^= 0xA001; } else { crc >>= 1; } } } return new byte[]{(byte) crc, (byte) (crc >> 8)}; } public static String bytesToHex(byte[] bytes) { StringBuilder sb = new StringBuilder(); for (byte b : bytes) { sb.append(String.format("%02X ", b)); } return sb.toString().trim(); } } package demo1; public class RegisterAddress { // 测量值类 public static final int TEMP_MEASURED = 0x0000; public static final int HUMIDITY_MEASURED = 0x0001; public static final int RPM_MEASURED = 0x000D; public static final int CO2_MEASURED = 0x0012; public static final int PRESSURE_MEASURED = 0x0013; public static final int O2_MEASURED = 0x00E8; // 控制设定类 public static final int RUN_CONTROL = 0x0002; public static final int TEMP_SETPOINT = 0x0003; public static final int TIMER_SETPOINT = 0x0006; public static final int HUMIDITY_SETPOINT = 0x0019; public static final int RPM_SETPOINT = 0x0018; public static final int LIGHT_GROUP1 = 0x001A; public static final int LIGHT_GROUP2 = 0x001E; public static final int LIGHT_GROUP3 = 0x001F; public static final int CO2_SETPOINT = 0x001B; public static final int O2_SETPOINT = 0x001C; public static final int PRESSURE_SETPOINT = 0x001D; // 状态与报警类 public static final int ALARM_STATUS = 0x000A; public static final int DEVICE_STATUS = 0x000B; // 连续读取 public static final int CONTINUOUS_READ = 0x00F8; } package demo1; import com.fazecast.jSerialComm.SerialPort; import com.fazecast.jSerialComm.SerialPortDataListener; import com.fazecast.jSerialComm.SerialPortEvent; public class SerialPortDataListenerImpl implements SerialPortDataListener { private final TemperatureMonitorGUI gui; private final TemperatureController controller; private final SerialPort serialPort; private final byte[] buffer = new byte[256]; private int bufferIndex = 0; public SerialPortDataListenerImpl(SerialPort serialPort, TemperatureMonitorGUI gui, TemperatureController controller) { this.serialPort = serialPort; this.gui = gui; this.controller = controller; } @Override public int getListeningEvents() { return SerialPort.LISTENING_EVENT_DATA_AVAILABLE; } @Override public void serialEvent(SerialPortEvent event) { if (event.getEventType() == SerialPort.LISTENING_EVENT_DATA_AVAILABLE) { byte[] newData = new byte[serialPort.bytesAvailable()]; int numRead = serialPort.readBytes(newData, newData.length); System.arraycopy(newData, 0, buffer, bufferIndex, numRead); bufferIndex += numRead; processBuffer(); } } private void processBuffer() { if (bufferIndex < 5) return; int functionCode = buffer[1] & 0xFF; if ((functionCode & 0x80) != 0) { if (bufferIndex >= 5) { handleError(); removeProcessedBytes(5); } return; } switch (functionCode) { case 0x03: handleReadResponse(); break; case 0x06: handleWriteResponse(); break; case 0x08: handleLoopbackResponse(); break; default: removeProcessedBytes(1); } } private void handleReadResponse() { int byteCount = buffer[2] & 0xFF; int frameLength = 3 + byteCount + 2; if (bufferIndex >= frameLength) { if (!verifyCRC(buffer, frameLength)) { System.err.println("CRC校验失败"); removeProcessedBytes(frameLength); return; } int registerAddr = ((buffer[2] & 0xFF) << 8) | (buffer[3] & 0xFF); switch (registerAddr) { case RegisterAddress.TEMP_MEASURED: int tempValue = ((buffer[4] & 0xFF) << 8) | (buffer[5] & 0xFF); double temperature = tempValue / 10.0; controller.updateTemperature(temperature); break; case RegisterAddress.HUMIDITY_MEASURED: int humidity = ((buffer[4] & 0xFF) << 8) | (buffer[5] & 0xFF); controller.updateHumidity(humidity); break; case RegisterAddress.CONTINUOUS_READ: handleContinuousReadResponse(); break; } removeProcessedBytes(frameLength); } } private void handleContinuousReadResponse() { int byteCount = buffer[2] & 0xFF; int frameLength = 3 + byteCount + 2; if (bufferIndex >= frameLength) { if (!verifyCRC(buffer, frameLength)) { System.err.println("连续读取CRC校验失败"); removeProcessedBytes(frameLength); return; } int index = 3; int runTime = ((buffer[index++] & 0xFF) << 8) | (buffer[index++] & 0xFF); int setTime = ((buffer[index++] & 0xFF) << 8) | (buffer[index++] & 0xFF); int tempMeasured = ((buffer[index++] & 0xFF) << 8) | (buffer[index++] & 0xFF); int tempSetpoint = ((buffer[index++] & 0xFF) << 8) | (buffer[index++] & 0xFF); int humidityMeasured = ((buffer[index++] & 0xFF) << 8) | (buffer[index++] & 0xFF); int humiditySetpoint = ((buffer[index++] & 0xFF) << 8) | (buffer[index++] & 0xFF); int lightGroup2 = buffer[index++] & 0xFF; int lightGroup1 = buffer[index++] & 0xFF; int lightGroup3 = buffer[index++] & 0xFF; lightGroup1 = buffer[index++] & 0xFF; // duplicate int rpmMeasured = ((buffer[index++] & 0xFF) << 8) | (buffer[index++] & 0xFF); int rpmSetpoint = ((buffer[index++] & 0xFF) << 8) | (buffer[index++] & 0xFF); int co2Measured = ((buffer[index++] & 0xFF) << 8) | (buffer[index++] & 0xFF); int co2Setpoint = ((buffer[index++] & 0xFF) << 8) | (buffer[index++] & 0xFF); int o2Measured = ((buffer[index++] & 0xFF) << 8) | (buffer[index++] & 0xFF); int o2Setpoint = ((buffer[index++] & 0xFF) << 8) | (buffer[index++] & 0xFF); int pressureMeasured = ((buffer[index++] & 0xFF) << 8) | (buffer[index++] & 0xFF); int pressureSetpoint = ((buffer[index++] & 0xFF) << 8) | (buffer[index++] & 0xFF); controller.updateTemperature(tempMeasured / 10.0); controller.updateHumidity(humidityMeasured); removeProcessedBytes(frameLength); } } private void handleWriteResponse() { int frameLength = 8; if (bufferIndex >= frameLength) { if (!verifyCRC(buffer, frameLength)) { System.err.println("写响应CRC校验失败"); removeProcessedBytes(frameLength); return; } int registerAddr = ((buffer[2] & 0xFF) << 8) | (buffer[3] & 0xFF); int value = ((buffer[4] & 0xFF) << 8) | (buffer[5] & 0xFF); System.out.println("寄存器写入成功: 地址=" + Integer.toHexString(registerAddr) + ", 值=" + Integer.toHexString(value)); gui.setStatus("寄存器写入成功"); removeProcessedBytes(frameLength); } } private void handleLoopbackResponse() { int frameLength = 8; if (bufferIndex >= frameLength) { if (!verifyCRC(buffer, frameLength)) { System.err.println("回路侦测CRC校验失败"); removeProcessedBytes(frameLength); return; } int subFunction = ((buffer[2] & 0xFF) << 8) | (buffer[3] & 0xFF); int data = ((buffer[4] & 0xFF) << 8) | (buffer[5] & 0xFF); System.out.println("回路侦测成功: 子功能=" + Integer.toHexString(subFunction) + ", 数据=" + Integer.toHexString(data)); gui.setStatus("回路侦测成功"); removeProcessedBytes(frameLength); } } private void handleError() { int errorCode = buffer[2] & 0xFF; String errorMessage = "错误响应: "; switch (errorCode) { case 0x01: errorMessage += "功能码错误"; break; case 0x02: errorMessage += "寄存器地址错误"; break; case 0x03: errorMessage += "资料内容值错误"; break; case 0x04: errorMessage += "控制器无法处理"; break; case 0x09: errorMessage += "CRC校验错误"; break; case 0x0A: errorMessage += "奇偶校验错误"; break; case 0x0C: errorMessage += "接收数据低于规定长度"; break; case 0x0D: errorMessage += "接收数据超过规定长度"; break; default: errorMessage += "未知错误码: " + errorCode; } System.err.println(errorMessage); gui.setStatus(errorMessage); } private boolean verifyCRC(byte[] data, int length) { byte[] frameData = new byte[length - 2]; System.arraycopy(data, 0, frameData, 0, length - 2); byte[] receivedCRC = new byte[]{data[length - 2], data[length - 1]}; byte[] calculatedCRC = ModbusRequestBuilder.calculateCRC(frameData); return receivedCRC[0] == calculatedCRC[0] && receivedCRC[1] == calculatedCRC[1]; } private void removeProcessedBytes(int count) { if (count > bufferIndex) count = bufferIndex; System.arraycopy(buffer, count, buffer, 0, bufferIndex - count); bufferIndex -= count; } } package demo1; class SerialRequest { public byte[] data; public boolean isManual; public SerialRequest(byte[] data, boolean isManual) { this.data = data; this.isManual = isManual; } } package demo1; import com.fazecast.jSerialComm.SerialPort; import static demo1.ModbusRequestBuilder.buildReadRequest; import static demo1.ModbusRequestBuilder.buildWriteRequest; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; public class TemperatureController { private final TemperatureMonitorGUI gui; private final String portName; private SerialPort serialPort; private volatile boolean running = false; // 请求队列 private final BlockingQueue<SerialRequest> requestQueue = new LinkedBlockingQueue<>(); private ScheduledExecutorService queueExecutor = null; public TemperatureController(TemperatureMonitorGUI gui, String portName) { this.gui = gui; this.portName = portName; } public void start() { new Thread(this::connectSerialPort).start(); } private void connectSerialPort() { serialPort = SerialPort.getCommPort(portName); serialPort.setComPortParameters(9600, 8, SerialPort.ONE_STOP_BIT, SerialPort.NO_PARITY); // 参数为4800/8/N/2时 // serialPort.setComPortParameters(4800, 8, SerialPort.TWO_STOP_BITS, SerialPort.NO_PARITY); serialPort.setComPortTimeouts(SerialPort.TIMEOUT_READ_SEMI_BLOCKING, 1000, 0); if (serialPort.openPort()) { System.out.println("✅ 成功打开串口"); gui.setStatus("已连接到 " + portName); serialPort.addDataListener(new SerialPortDataListenerImpl(serialPort, gui, this)); startQueueConsumer(); // 启动请求消费者 startTemperaturePolling(); // 启动轮询 } else { System.err.println("❌ 无法打开串口"); gui.setStatus("无法打开串口:" + portName); } } private void startQueueConsumer() { queueExecutor = java.util.concurrent.Executors.newSingleThreadScheduledExecutor(); queueExecutor.submit(() -> { while (!Thread.interrupted()) { try { SerialRequest request = requestQueue.take(); sendRequest(request.data); if (request.isManual) { Thread.sleep(500); // 手动操作稍作延迟 } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } }); } private void startTemperaturePolling() { queueExecutor.scheduleAtFixedRate(() -> { enqueueRequest(buildReadRequest((byte) 0x01, RegisterAddress.TEMP_MEASURED), false); }, 0, 1, TimeUnit.SECONDS); } public void readRegister(byte slaveId, int registerAddr) { enqueueRequest(buildReadRequest(slaveId, registerAddr), true); } public void writeRegister(byte slaveId, int registerAddr, int value) { enqueueRequest(buildWriteRequest(slaveId, registerAddr, value), true); } public void loopbackTest(byte slaveId) { enqueueRequest(new byte[]{slaveId, 0x08, 0x00, 0x00, 0x12, 0x34}, true); } public void continuousRead(byte slaveId) { enqueueRequest(new byte[]{slaveId, 0x03, 0x00, (byte) 0xF8, 0x00, 0x14}, true); } private void enqueueRequest(byte[] data, boolean isManual) { requestQueue.offer(new SerialRequest(data, isManual)); } public void sendRequest(byte[] data) { if (serialPort == null || !serialPort.isOpen()) { System.err.println("串口未打开,无法发送数据"); return; } byte[] crc = ModbusRequestBuilder.calculateCRC(data); byte[] fullRequest = new byte[data.length + 2]; System.arraycopy(data, 0, fullRequest, 0, data.length); fullRequest[data.length] = crc[0]; // CRC低字节 fullRequest[data.length + 1] = crc[1]; // CRC高字节 serialPort.writeBytes(fullRequest, fullRequest.length); System.out.println("发送请求: " + ModbusRequestBuilder.bytesToHex(fullRequest)); } public void updateTemperature(double temperature) { gui.updateTemperature(temperature); } public void updateHumidity(int humidity) { gui.updateHumidity(humidity); } } package demo1; import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; public class TemperatureMonitorGUI extends JFrame { private JLabel temperatureLabel; private JLabel humidityLabel; private JLabel statusLabel; private JButton connectButton; private JButton readButton; private JButton writeButton; private JButton loopbackButton; private JButton continuousReadButton; private JComboBox<String> portComboBox; private TemperatureController controller; // 输入组件 private JTextField slaveIdField; private JComboBox<String> functionCodeCombo; private JTextField registerAddrField; private JTextField valueField; // 新增输入字段 private JTextField temperatureSetpointField; private JTextField timerSetpointField; public TemperatureMonitorGUI() { setTitle("MODBUS RTU 温控系统"); setSize(800, 600); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setLayout(new GridLayout(14, 2, 5, 5)); // 行数增加2行 // 串口选择 add(new JLabel("串口:")); portComboBox = new JComboBox<>(); refreshPortList(); add(portComboBox); // 地址码输入 add(new JLabel("地址码 (HEX):")); slaveIdField = new JTextField("01"); add(slaveIdField); // 功能码选择 add(new JLabel("功能码:")); functionCodeCombo = new JComboBox<>(new String[]{"03H: 读寄存器", "06H: 写寄存器", "08H: 回路侦测"}); add(functionCodeCombo); // 寄存器地址 add(new JLabel("寄存器地址 (HEX):")); registerAddrField = new JTextField("0000"); add(registerAddrField); // 值 add(new JLabel("值 (HEX):")); valueField = new JTextField("0000"); add(valueField); // 目标温度 add(new JLabel("目标温度 (HEX):")); temperatureSetpointField = new JTextField("0000"); add(temperatureSetpointField); // 加热时间 add(new JLabel("加热时间 (HEX):")); timerSetpointField = new JTextField("0000"); add(timerSetpointField); // 操作按钮 JPanel buttonPanel = new JPanel(new GridLayout(1, 5, 5, 5)); connectButton = new JButton("连接串口"); readButton = new JButton("读取"); writeButton = new JButton("写入"); loopbackButton = new JButton("回路测试"); continuousReadButton = new JButton("连续读取"); buttonPanel.add(connectButton); buttonPanel.add(readButton); buttonPanel.add(writeButton); buttonPanel.add(loopbackButton); buttonPanel.add(continuousReadButton); add(buttonPanel); // 状态栏 statusLabel = new JLabel("状态: 未连接", SwingConstants.CENTER); add(statusLabel); // 当前温度显示 temperatureLabel = new JLabel("当前温度: --°C", SwingConstants.CENTER); temperatureLabel.setFont(new Font("Arial", Font.BOLD, 24)); add(temperatureLabel); // 湿度显示 humidityLabel = new JLabel("当前湿度: --%", SwingConstants.CENTER); humidityLabel.setFont(new Font("Arial", Font.BOLD, 24)); add(humidityLabel); // 初始化控制器 controller = null; // 事件绑定 connectButton.addActionListener(e -> { String selectedPort = (String) portComboBox.getSelectedItem(); if (selectedPort != null && controller == null) { controller = new TemperatureController(this, selectedPort); controller.start(); } }); readButton.addActionListener(e -> { if (controller != null) { try { int slaveId = Integer.parseInt(slaveIdField.getText(), 16); int registerAddr = Integer.parseInt(registerAddrField.getText(), 16); controller.readRegister((byte) slaveId, registerAddr); } catch (NumberFormatException ex) { JOptionPane.showMessageDialog(this, "请输入有效的十六进制数字!"); } } }); writeButton.addActionListener(e -> { if (controller != null) { try { int slaveId = Integer.parseInt(slaveIdField.getText(), 16); int registerAddr = Integer.parseInt(registerAddrField.getText(), 16); int value = Integer.parseInt(valueField.getText(), 16); // 写入用户指定的寄存器 controller.writeRegister((byte) slaveId, registerAddr, value); // 如果是温度设定或定时器设定,则额外写入对应寄存器 if (registerAddr == RegisterAddress.TEMP_SETPOINT) { int tempSetpointValue = Integer.parseInt(temperatureSetpointField.getText(), 16); controller.writeRegister((byte) slaveId, RegisterAddress.TEMP_SETPOINT, tempSetpointValue * 10); // 以 0.1°C 为单位 } if (registerAddr == RegisterAddress.TIMER_SETPOINT) { int timerSetpointValue = Integer.parseInt(timerSetpointField.getText(), 16); controller.writeRegister((byte) slaveId, RegisterAddress.TIMER_SETPOINT, timerSetpointValue); } } catch (NumberFormatException ex) { JOptionPane.showMessageDialog(this, "请输入有效的十六进制数字!"); } } }); loopbackButton.addActionListener(e -> { if (controller != null) { try { int slaveId = Integer.parseInt(slaveIdField.getText(), 16); controller.loopbackTest((byte) slaveId); } catch (NumberFormatException ex) { JOptionPane.showMessageDialog(this, "请输入有效的十六进制数字!"); } } }); continuousReadButton.addActionListener(e -> { if (controller != null) { try { int slaveId = Integer.parseInt(slaveIdField.getText(), 16); controller.continuousRead((byte) slaveId); } catch (NumberFormatException ex) { JOptionPane.showMessageDialog(this, "请输入有效的十六进制数字!"); } } }); } public void refreshPortList() { portComboBox.removeAllItems(); for (com.fazecast.jSerialComm.SerialPort port : com.fazecast.jSerialComm.SerialPort.getCommPorts()) { portComboBox.addItem(port.getSystemPortName()); } } public void updateTemperature(double temperature) { temperatureLabel.setText(String.format("当前温度: %.1f°C", temperature)); } public void updateHumidity(int humidity) { humidityLabel.setText(String.format("当前湿度: %d%%", humidity)); } public void setStatus(String status) { statusLabel.setText("状态: " + status); } } 新增 IEEE 754 浮点数写入功能,并允许用户选择使用 整数写入(DEC/HEX) 或 浮点数写入(IEEE 754)
07-16
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值