package dell_system; import com.fazecast.jSerialComm.SerialPort; import Dell55aa.Dell55AA12HighPerf; import Dell55aa.Dell55AA12HighPerf.ParseResult; 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 hexData = bytesToHex(data).toUpperCase(); // ʹÓÃDell55AA12HighPerf½âÎö ParseResult result = Dell55AA12HighPerf.parse(hexData); if (result != null) { // ¹¹½¨×´Ì¬×Ö·û´® String buttonStatus = result.sosButtonPressed ? messages.getString("yes") : messages.getString("no"); String staticStatus = result.isStatic ? messages.getString("yes") : messages.getString("no"); String sleepStatus = result.isSleeping ? messages.getString("yes") : messages.getString("no"); String vibrationStatus = (result.vibrationState == 1) ? messages.getString("on") : messages.getString("off"); String uwbStatus = result.tagRemoved ? messages.getString("off") : messages.getString("on"); // ¹¹½¨»ùÕ¾ÐÅÏ¢ StringBuilder anchorIds = new StringBuilder(); StringBuilder distances = new StringBuilder(); StringBuilder anchorPowers = new StringBuilder(); for (int i = 0; i < result.anchorCount; i++) { if (i > 0) { anchorIds.append(","); distances.append(","); anchorPowers.append(","); } anchorIds.append(result.anchorIds[i]); distances.append(result.distances[i]); anchorPowers.append(result.anchorPowers[i]); } // ¸ñʽ»¯Êä³ö StringBuilder parsedOutput = new StringBuilder(); // Ìí¼Óʱ¼ä´Á£¨Èç¹û¹´Ñ¡£© if (chkTimestamp.isSelected()) { parsedOutput.append("[").append(sdf.format(new Date())).append("]\n"); } parsedOutput.append(String.format( "%s: %d\n%s: %d\n%s: %d%%\n%s: %s[%s], %s[%s], %s[%s], %s[%s], %s[%s]\n%s: %d\n%s: %d\n%s: (%s)\n%s: (%s)\n%s: (%s)\n\n", messages.getString("label.id"), result.tagId, messages.getString("label.sequence"), result.sequenceNum, messages.getString("label.power"), result.power, messages.getString("label.status"), messages.getString("status.button"), buttonStatus, messages.getString("status.static"), staticStatus, messages.getString("status.sleeping"), sleepStatus, messages.getString("status.vibration"), vibrationStatus, messages.getString("status.uwb_switch"), uwbStatus, messages.getString("label.tag_height"), result.tagHeight, messages.getString("label.anchor_count"), result.anchorCount, messages.getString("label.anchor_ids"), anchorIds.toString(), messages.getString("label.distances"), distances.toString(), messages.getString("label.anchor_powers"), anchorPowers.toString() )); txtParsedData.append(parsedOutput.toString()); txtParsedData.setCaretPosition(txtParsedData.getDocument().getLength()); lblParseStatus.setText(String.format("%s (Packets: %d)", messages.getString("parser.ready"), packetCounter)); } else { StringBuilder invalidMsg = new StringBuilder(); // Ìí¼Óʱ¼ä´Á£¨Èç¹û¹´Ñ¡£© if (chkTimestamp.isSelected()) { invalidMsg.append("[").append(sdf.format(new Date())).append("]\n"); } invalidMsg.append(String.format("[Packet #%d] Not a valid 55AA12 packet\n\n", packetCounter)); txtParsedData.append(invalidMsg.toString()); lblParseStatus.setText(String.format("Parser: Invalid (Packets: %d)", packetCounter)); } } catch (Exception e) { StringBuilder errorMsg = new StringBuilder(); // Ìí¼Óʱ¼ä´Á£¨Èç¹û¹´Ñ¡£© if (chkTimestamp.isSelected()) { errorMsg.append("[").append(sdf.format(new Date())).append("]\n"); } errorMsg.append(String.format("[Packet #%d] Parse error: %s\n\n", packetCounter, e.getMessage())); txtParsedData.append(errorMsg.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(); } }); } } // ÐÂÔö¸¨Öú·½·¨£º×Ö½ÚÊý×éתʮÁù½øÖÆ×Ö·û´® private String bytesToHex(byte[] bytes) { StringBuilder sb = new StringBuilder(); for (byte b : bytes) { sb.append(String.format("%02X", b)); } return sb.toString(); } // ÐÞ¸Ä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")); } }