package dell_system; import com.fazecast.jSerialComm.SerialPort; import javax.swing.*; import javax.swing.border.TitledBorder; import javax.swing.event.PopupMenuEvent; import javax.swing.event.PopupMenuListener; import java.awt.*; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.io.PrintWriter; import java.text.SimpleDateFormat; import java.util.Date; import java.util.ResourceBundle; public class SerialCommPanel extends JPanel { private static final long serialVersionUID = 1L; private final ResourceBundle messages; private final SerialPortService serialService = new SerialPortService(); // ÐÂÔö×Ö¶Î private int packetCounter = 0; private JLabel lblParseStatus; private JCheckBox chkEnableParsing; private JLabel lblPacketCount; // ÐÂÔö£ºÊý¾Ý°ü¼ÆÊý±êÇ© // UI ×é¼þ private JComboBox cbPorts; private JComboBox cbBaudRate; private JCheckBox chkHexDisplay; private JCheckBox chkTimestamp; private JCheckBox chkAppendNewline; private JCheckBox chkHexSend; private JCheckBox chkTimedSend; private JTextField txtInterval; private JButton btnOpenSerial; private JButton btnStart; private JButton btnPause; private JButton btnClear; private JTextField txtSendData; private JButton btnSend; private JTextArea txtRawData; private JTextArea txtParsedData; private JScrollPane rawScrollPane; private JScrollPane parsedScrollPane; private final SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS"); public SerialCommPanel(ResourceBundle messages) { this.messages = messages; initUI(); } /* ================= UI ³õʼ»¯ ================= */ private void initUI() { setLayout(new GridBagLayout()); GridBagConstraints gbc = new GridBagConstraints(); gbc.insets = new Insets(5, 10, 5, 10); gbc.anchor = GridBagConstraints.WEST; gbc.fill = GridBagConstraints.HORIZONTAL; setBackground(new Color(240, 245, 249)); /* ===== ´®¿ÚÑ¡ÔñºÍ²¨ÌØÂÊ ===== */ gbc.gridx = 0; gbc.gridy = 0; gbc.gridwidth = 1; gbc.weightx = 0.3; JPanel portPanel = new JPanel(new BorderLayout(5, 0)); portPanel.setBackground(new Color(240, 245, 249)); JLabel lblPort = new JLabel(messages.getString("SERIAL_PORT")); lblPort.setFont(new Font("ËÎÌå", Font.BOLD, 12)); portPanel.add(lblPort, BorderLayout.WEST); cbPorts = new JComboBox<>(); cbPorts.setBackground(new Color(200, 255, 200)); scanSerialPorts(); cbPorts.addPopupMenuListener(new PopupMenuListener() { @Override public void popupMenuWillBecomeVisible(PopupMenuEvent e) { scanSerialPorts(); } @Override public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { } @Override public void popupMenuCanceled(PopupMenuEvent e) { } }); portPanel.add(cbPorts, BorderLayout.CENTER); add(portPanel, gbc); gbc.gridx++; JPanel baudPanel = new JPanel(new BorderLayout(5, 0)); baudPanel.setBackground(new Color(240, 245, 249)); JLabel lblBaud = new JLabel(messages.getString("BAUD_RATE")); lblBaud.setFont(new Font("ËÎÌå", Font.BOLD, 12)); baudPanel.add(lblBaud, BorderLayout.WEST); cbBaudRate = new JComboBox<>(new String[]{ "19200", "38400", "57600", "115200", "230400", "460800", "921600" }); cbBaudRate.setBackground(new Color(255, 200, 255)); cbBaudRate.setSelectedItem("115200"); baudPanel.add(cbBaudRate, BorderLayout.CENTER); add(baudPanel, gbc); gbc.gridx++; btnOpenSerial = createStyledButton(messages.getString("OPEN_SERIAL"), new Color(70, 130, 180)); btnOpenSerial.addActionListener(e -> toggleSerialPort()); add(btnOpenSerial, gbc); /* ===== ԭʼÊý¾ÝÏÔÊ¾ÇøÓò ===== */ gbc.gridx = 0; gbc.gridy++; gbc.gridwidth = 3; gbc.weighty = 0.5; gbc.fill = GridBagConstraints.BOTH; txtRawData = new JTextArea(); txtRawData.setFont(new Font("Monospaced", Font.PLAIN, 12)); txtRawData.setEditable(false); txtRawData.setBackground(new Color(255, 200, 200)); txtRawData.setLineWrap(true); txtRawData.setWrapStyleWord(false); rawScrollPane = new JScrollPane(txtRawData); rawScrollPane.setBorder(BorderFactory.createTitledBorder( BorderFactory.createLineBorder(Color.DARK_GRAY), messages.getString("RAW_DATA_WINDOW"), TitledBorder.LEFT, TitledBorder.TOP, new Font("ËÎÌå", Font.BOLD, 12), Color.BLACK)); add(rawScrollPane, gbc); /* ===== ½âÎöÊý¾ÝÏÔÊ¾ÇøÓò ===== */ gbc.gridy++; txtParsedData = new JTextArea(); txtParsedData.setFont(new Font("Monospaced", Font.PLAIN, 12)); txtParsedData.setEditable(false); txtParsedData.setBackground(new Color(255, 255, 200)); txtParsedData.setLineWrap(true); txtParsedData.setWrapStyleWord(false); parsedScrollPane = new JScrollPane(txtParsedData); parsedScrollPane.setBorder(BorderFactory.createTitledBorder( BorderFactory.createLineBorder(Color.DARK_GRAY), messages.getString("PARSED_DATA_WINDOW"), TitledBorder.LEFT, TitledBorder.TOP, new Font("ËÎÌå", Font.BOLD, 12), Color.BLACK)); add(parsedScrollPane, gbc); /* ===== Ϊ½âÎö´°¿ÚÌí¼ÓÓÒ¼ü²Ëµ¥ ===== */ JPopupMenu parsedPopup = new JPopupMenu(); JMenuItem clearItem = new JMenuItem(messages.getString("CLEAR")); JMenuItem exportItem = new JMenuItem(messages.getString("EXPORT")); clearItem.addActionListener(e -> txtParsedData.setText("")); exportItem.addActionListener(e -> exportParsedData()); parsedPopup.add(clearItem); parsedPopup.add(exportItem); txtParsedData.setComponentPopupMenu(parsedPopup); /* ===== ¿ØÖƺÍÏÔʾѡÏî ===== */ gbc.gridy++; gbc.weighty = 0; gbc.fill = GridBagConstraints.HORIZONTAL; JPanel controlPanel = new JPanel(new BorderLayout(10, 0)); controlPanel.setBackground(new Color(240, 245, 249)); // ×ó²à¿ØÖư´Å¥ JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 10, 5)); buttonPanel.setBackground(new Color(240, 245, 249)); btnStart = createStyledButton(messages.getString("START"), new Color(70, 130, 180)); btnPause = createStyledButton(messages.getString("PAUSE"), new Color(218, 165, 32)); btnClear = createStyledButton(messages.getString("CLEAR"), new Color(205, 92, 92)); btnStart.setEnabled(true); btnPause.setEnabled(false); btnStart.addActionListener(e -> startCapture()); btnPause.addActionListener(e -> pauseCapture()); btnClear.addActionListener(e -> clearDisplay()); buttonPanel.add(btnStart); buttonPanel.add(btnPause); buttonPanel.add(btnClear); // ÐÂÔö£ºÊý¾Ý°ü¼ÆÊý±êÇ© lblPacketCount = new JLabel(String.format(messages.getString("packet.count.format"), 0)); lblPacketCount.setFont(new Font("ËÎÌå", Font.BOLD, 12)); lblPacketCount.setForeground(new Color(0, 100, 0)); // ÉîÂÌÉ« buttonPanel.add(lblPacketCount); controlPanel.add(buttonPanel, BorderLayout.WEST); // ÓÒ²àÏÔʾѡÏî JPanel checkboxPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 10, 5)); checkboxPanel.setBackground(new Color(240, 245, 249)); chkHexDisplay = createStyledCheckbox(messages.getString("HEX_DISPLAY")); chkTimestamp = createStyledCheckbox(messages.getString("TIMESTAMP")); chkAppendNewline = createStyledCheckbox(messages.getString("APPEND_NEWLINE")); chkHexSend = createStyledCheckbox(messages.getString("HEX_SEND")); chkTimedSend = createStyledCheckbox(messages.getString("TIMED_SEND")); chkHexDisplay.setSelected(true); chkTimestamp.setSelected(true); chkAppendNewline.setSelected(true); txtInterval = new JTextField("1000", 5); txtInterval.setEnabled(false); txtInterval.setFont(new Font("ËÎÌå", Font.PLAIN, 12)); txtInterval.setBackground(Color.WHITE); chkTimedSend.addActionListener(e -> txtInterval.setEnabled(chkTimedSend.isSelected())); // ÐÂÔö½âÎö¿ØÖÆÑ¡Ïî chkEnableParsing = createStyledCheckbox(messages.getString("ENABLE_PARSING")); chkEnableParsing.setSelected(true); // ɾ³ý½âÎöÆ÷ÏÂÀ­¿ò¼°Æä±êÇ© checkboxPanel.add(chkHexDisplay); checkboxPanel.add(chkTimestamp); checkboxPanel.add(chkAppendNewline); checkboxPanel.add(chkHexSend); checkboxPanel.add(chkTimedSend); checkboxPanel.add(new JLabel(messages.getString("INTERVAL_MS"))); checkboxPanel.add(txtInterval); checkboxPanel.add(chkEnableParsing); controlPanel.add(checkboxPanel, BorderLayout.EAST); // ÐÂÔö½âÎö״ָ̬ʾÆ÷ JPanel statusPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); statusPanel.setBackground(new Color(240, 245, 249)); lblParseStatus = new JLabel(messages.getString("parser.ready")); lblParseStatus.setFont(new Font("ËÎÌå", Font.PLAIN, 12)); lblParseStatus.setForeground(Color.BLUE); statusPanel.add(lblParseStatus); controlPanel.add(statusPanel, BorderLayout.CENTER); add(controlPanel, gbc); /* ===== ·¢ËÍÇøÓò ===== */ gbc.gridy++; JPanel sendPanel = new JPanel(new BorderLayout(10, 5)); sendPanel.setBackground(new Color(240, 245, 249)); txtSendData = new JTextField(); txtSendData.setBackground(new Color(200, 200, 255)); txtSendData.setFont(new Font("ËÎÌå", Font.PLAIN, 12)); btnSend = createStyledButton(messages.getString("SEND"), new Color(60, 179, 113)); btnSend.addActionListener(e -> sendData()); sendPanel.add(new JLabel(messages.getString("SEND_DATA")), BorderLayout.WEST); sendPanel.add(txtSendData, BorderLayout.CENTER); sendPanel.add(btnSend, BorderLayout.EAST); add(sendPanel, gbc); addNotify(); } /* ================= ɨÃè´®¿ÚÁбí ================= */ private void scanSerialPorts() { cbPorts.removeAllItems(); SerialPort[] ports = SerialPort.getCommPorts(); if (ports.length == 0) { cbPorts.addItem("No port found"); cbPorts.setEnabled(false); return; } for (SerialPort p : ports) { String item = p.getSystemPortName(); String desc = p.getPortDescription(); if (desc != null && !desc.trim().isEmpty()) { item += " (" + desc + ")"; } cbPorts.addItem(item); } cbPorts.setEnabled(true); } /* ================= ´ò¿ª/¹Ø±Õ´®¿Ú ================= */ private void toggleSerialPort() { if (serialService.isOpen()) { serialService.close(); btnOpenSerial.setText(messages.getString("OPEN_SERIAL")); btnStart.setEnabled(true); btnPause.setEnabled(false); } else { String selected = (String) cbPorts.getSelectedItem(); if (selected == null || selected.startsWith("No port")) { JOptionPane.showMessageDialog(this, messages.getString("SELECT_PORT_ERROR"), messages.getString("ERROR"), JOptionPane.ERROR_MESSAGE); return; } String portName = selected.split(" ")[0]; int baud = Integer.parseInt((String) cbBaudRate.getSelectedItem()); if (serialService.open(portName, baud)) { btnOpenSerial.setText(messages.getString("CLOSE_SERIAL")); startCapture(); } else { JOptionPane.showMessageDialog(this, messages.getString("OPEN_PORT_ERROR"), messages.getString("ERROR"), JOptionPane.ERROR_MESSAGE); } } } /* ================= Êý¾Ý½ÓÊÕ¿ØÖÆ ================= */ private void startCapture() { if (!serialService.isOpen()) return; serialService.startCapture(this::appendRawData); btnStart.setEnabled(false); btnPause.setEnabled(true); } private void pauseCapture() { serialService.setPaused(!serialService.isPaused()); btnPause.setText(serialService.isPaused() ? messages.getString("RESUME") : messages.getString("PAUSE")); } /* ================= ½âÎöÊý¾ÝÏÔʾ ================= */ private void appendParsedData(byte[] data) { if (!chkEnableParsing.isSelected()) return; SwingUtilities.invokeLater(() -> { packetCounter++; lblPacketCount.setText(String.format(messages.getString("packet.count.format"), packetCounter)); try { String parsed = SerialPacketParser.parse(data, messages, chkTimestamp.isSelected()); txtParsedData.append(parsed); txtParsedData.setCaretPosition(txtParsedData.getDocument().getLength()); lblParseStatus.setText( String.format("%s (Packets: %d)", messages.getString("parser.ready"), packetCounter)); } catch (Exception ex) { StringBuilder sb = new StringBuilder(); if (chkTimestamp.isSelected()) sb.append('[').append(sdf.format(new Date())).append("]\n"); sb.append(String.format("[Packet #%d] Parse error: %s\n\n", packetCounter, ex.getMessage())); txtParsedData.append(sb.toString()); lblParseStatus.setText(String.format("Parser: Error (Packets: %d)", packetCounter)); } }); } /* ================= UI ¸üР================= */ private void appendRawData(byte[] bytes) { SwingUtilities.invokeLater(() -> { StringBuilder sb = new StringBuilder(); if (chkTimestamp.isSelected()) { sb.append("[").append(sdf.format(new Date())).append("] "); } if (chkHexDisplay.isSelected()) { for (byte b : bytes) { sb.append(String.format("%02X ", b)); } } else { for (byte b : bytes) { if (b != 0) sb.append((char) b); } } sb.append("\n"); txtRawData.append(sb.toString()); txtRawData.setCaretPosition(txtRawData.getDocument().getLength()); // ´¥·¢½âÎö appendParsedData(bytes); }); } private void sendData() { String data = txtSendData.getText().trim(); if (data.isEmpty()) { JOptionPane.showMessageDialog(this, messages.getString("EMPTY_SEND_DATA"), messages.getString("WARNING"), JOptionPane.WARNING_MESSAGE); return; } byte[] sendBytes; if (chkHexSend.isSelected()) { try { sendBytes = hexStringToBytes(data); if (sendBytes == null) { throw new IllegalArgumentException("HEX¸ñʽ´íÎó"); } } catch (Exception ex) { JOptionPane.showMessageDialog(this, "HEX¸ñʽ´íÎó£¬ÇëÊäÈëÓÐЧµÄÊ®Áù½øÖÆÊý¾Ý£¨È磺01 0A FF£©", "¸ñʽ´íÎó", JOptionPane.ERROR_MESSAGE); return; } } else { sendBytes = data.getBytes(); if (chkAppendNewline.isSelected()) { String newline = "\r\n"; byte[] newlineBytes = newline.getBytes(); byte[] combined = new byte[sendBytes.length + newlineBytes.length]; System.arraycopy(sendBytes, 0, combined, 0, sendBytes.length); System.arraycopy(newlineBytes, 0, combined, sendBytes.length, newlineBytes.length); sendBytes = combined; } } if (!serialService.send(sendBytes)) { JOptionPane.showMessageDialog(this, messages.getString("SEND_ERROR"), messages.getString("ERROR"), JOptionPane.ERROR_MESSAGE); } } /* ================= µ¼³ö½âÎöÊý¾Ý ================= */ private void exportParsedData() { JFileChooser fileChooser = new JFileChooser(); fileChooser.setDialogTitle(messages.getString("EXPORT_PARSED_DATA")); if (fileChooser.showSaveDialog(this) == JFileChooser.APPROVE_OPTION) { try (PrintWriter writer = new PrintWriter(fileChooser.getSelectedFile())) { writer.write(txtParsedData.getText()); JOptionPane.showMessageDialog(this, messages.getString("EXPORT_SUCCESS"), messages.getString("INFO"), JOptionPane.INFORMATION_MESSAGE); } catch (Exception ex) { JOptionPane.showMessageDialog(this, messages.getString("EXPORT_FAILED") + ": " + ex.getMessage(), messages.getString("ERROR"), JOptionPane.ERROR_MESSAGE); } } } /* ================= ¸¨Öú·½·¨ ================= */ private byte[] hexStringToBytes(String hexString) { if (hexString == null || hexString.trim().isEmpty()) { return null; } hexString = hexString.replaceAll("\\s+", ""); if (hexString.length() % 2 != 0) { return null; } try { byte[] result = new byte[hexString.length() / 2]; for (int i = 0; i < result.length; i++) { int index = i * 2; String byteStr = hexString.substring(index, index + 2); result[i] = (byte) Integer.parseInt(byteStr, 16); } return result; } catch (NumberFormatException e) { return null; } } private JButton createStyledButton(String text, Color bg) { JButton b = new JButton(text); b.setFont(new Font("ËÎÌå", Font.BOLD, 12)); b.setBackground(bg); b.setForeground(Color.WHITE); b.setFocusPainted(false); b.setBorder(BorderFactory.createCompoundBorder( BorderFactory.createLineBorder(bg.darker(), 1), BorderFactory.createEmptyBorder(5, 15, 5, 15))); return b; } private JCheckBox createStyledCheckbox(String text) { JCheckBox c = new JCheckBox(text); c.setFont(new Font("ËÎÌå", Font.PLAIN, 12)); c.setBackground(new Color(240, 245, 249)); c.setForeground(new Color(70, 70, 70)); return c; } /* ================= ´°¿Ú¹Ø±Õ´¦Àí ================= */ @Override public void addNotify() { super.addNotify(); java.awt.Window w = SwingUtilities.getWindowAncestor(this); if (w != null) { w.addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { serialService.close(); } }); } } // ÐÞ¸ÄclearDisplay·½·¨ private void clearDisplay() { txtRawData.setText(""); txtParsedData.setText(""); packetCounter = 0; lblPacketCount.setText(String.format(messages.getString("packet.count.format"), 0)); lblParseStatus.setText(messages.getString("parser.ready")); } }