From a96c7af43ebad18cde969333c514b9a5b9335647 Mon Sep 17 00:00:00 2001 From: Jose Damico Date: Fri, 5 Jan 2024 00:39:30 -0300 Subject: [PATCH] improved UI and messages --- .../java/org/jdamico/javax25/BasicHelper.java | 155 ++++++++++ .../java/org/jdamico/javax25/Constants.java | 9 + src/main/java/org/jdamico/javax25/GuiApp.java | 268 +++++++++++------- .../jdamico/javax25/PacketHandlerImpl.java | 5 +- .../jdamico/javax25/soundcard/Soundcard.java | 21 +- .../{ => threads}/GuiDecoderThread.java | 15 +- 6 files changed, 352 insertions(+), 121 deletions(-) create mode 100644 src/main/java/org/jdamico/javax25/BasicHelper.java create mode 100644 src/main/java/org/jdamico/javax25/Constants.java rename src/main/java/org/jdamico/javax25/{ => threads}/GuiDecoderThread.java (72%) diff --git a/src/main/java/org/jdamico/javax25/BasicHelper.java b/src/main/java/org/jdamico/javax25/BasicHelper.java new file mode 100644 index 0000000..e023b48 --- /dev/null +++ b/src/main/java/org/jdamico/javax25/BasicHelper.java @@ -0,0 +1,155 @@ +package org.jdamico.javax25; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.lang.management.ManagementFactory; +import java.net.URISyntaxException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Locale; +import java.util.concurrent.TimeUnit; + + +public class BasicHelper { + + private static BasicHelper INSTANCE = null; + private BasicHelper(){} + public static BasicHelper getInstance(){ + if(null == INSTANCE) INSTANCE = new BasicHelper(); + return INSTANCE; + } + + public String dateToString(Date date, String format){ + String strDate = null; + try { + SimpleDateFormat formatter = new SimpleDateFormat(format); + strDate = formatter.format(date); + }catch (NullPointerException e) {} + return strDate; + + } + + public void posixKill(String signal, String pid) { + + try { + Runtime runtime = Runtime.getRuntime(); + runtime.exec("kill -"+signal+" "+pid); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public Date stringToDate(String dateStr, String format) throws Exception{ + + DateFormat dateFormat = new SimpleDateFormat(format, Locale.ENGLISH); + Date date = null; + + date = dateFormat.parse(dateStr); + + return date; + } + + public long getDiffHoursBetweenDates(Date init, Date end) { + long diff = end.getTime() - init.getTime(); + diff = TimeUnit.HOURS.convert(diff, TimeUnit.MILLISECONDS); + return diff; + } + + public String readTextFileToString(File source) throws Exception { + StringBuffer output = null; + + + + if(source != null && source.exists() && source.isFile()) { + Path path = source.toPath(); + try { + List lst = Files.readAllLines(path); + output = new StringBuffer(); + for (int i = 0; i < lst.size(); i++) { + output.append(lst.get(i)+"\n"); + } + } catch (IOException e) { + throw new Exception("invalid file"); + } + }else throw new Exception("invalid file"); + + + + return output.toString(); + } + + public void writeStrToFile(String str, String fileName) throws IOException{ + + File file = new File(fileName); + writeStrToFile(str, file); + } + + + public void writeStrToFile(String str, File file) throws IOException{ + + FileWriter fw = null; + BufferedWriter out = null; + try { + fw = new FileWriter(file); + out = new BufferedWriter(fw); + out.write(str); + }finally{ + if(out != null) + + out.close(); + + if(fw != null) + + fw.close(); + + } + } + + + public String listToString(List lst) { + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < lst.size(); i++) { + if(i + 1 == lst.size()) sb.append(lst.get(i)); + else sb.append(lst.get(i)+", "); + } + return sb.toString(); + } + + public List stringToListCli(String defaultRtl433Cli) { + List lst = new ArrayList(); + defaultRtl433Cli = defaultRtl433Cli.replaceAll(" ", " "); + String[] rtlStrArray = defaultRtl433Cli.split(" "); + for (String cliPart : rtlStrArray) { + lst.add(cliPart); + } + return lst; + } + + public String getAbsoluteRunningPath() { + Path currentRelativePath = Paths.get(""); + return currentRelativePath.toAbsolutePath().toString(); + } + + public String getAbsoluteMainJarPath() throws URISyntaxException { + return new File(App.class.getProtectionDomain().getCodeSource().getLocation().toURI()).getPath(); + } + + public String getCurrentPid() { + String ret = null; + String jvmName = ManagementFactory.getRuntimeMXBean().getName(); + int index = jvmName.indexOf('@'); + ret = Long.toString(Long.parseLong(jvmName.substring(0, index))); + + + return ret; + } + +} diff --git a/src/main/java/org/jdamico/javax25/Constants.java b/src/main/java/org/jdamico/javax25/Constants.java new file mode 100644 index 0000000..d65c2ca --- /dev/null +++ b/src/main/java/org/jdamico/javax25/Constants.java @@ -0,0 +1,9 @@ +package org.jdamico.javax25; + +public interface Constants { + + public static final String DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss"; + public static final String APP_NAME = "JavaX25 Tester"; + public static final String APP_VERSION = "0.0.2"; + +} diff --git a/src/main/java/org/jdamico/javax25/GuiApp.java b/src/main/java/org/jdamico/javax25/GuiApp.java index a81708a..74fa810 100644 --- a/src/main/java/org/jdamico/javax25/GuiApp.java +++ b/src/main/java/org/jdamico/javax25/GuiApp.java @@ -6,6 +6,7 @@ import java.awt.event.ActionListener; import java.util.List; +import javax.sound.sampled.Mixer; import javax.swing.BorderFactory; import javax.swing.ImageIcon; import javax.swing.JButton; @@ -13,6 +14,7 @@ import javax.swing.JComponent; import javax.swing.JFrame; import javax.swing.JLabel; +import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTextArea; @@ -22,16 +24,19 @@ import org.jdamico.javax25.ax25.Afsk1200Modulator; import org.jdamico.javax25.ax25.Packet; import org.jdamico.javax25.soundcard.Soundcard; +import org.jdamico.javax25.threads.GuiDecoderThread; public class GuiApp extends JPanel implements ActionListener { - + private JTextArea textArea = new JTextArea(40, 80); private JTextField inputPacketField; private JTextField callsign; private JTextField destination; private JTextField digipath; - - + private JLabel audioIn; + private JLabel audioOut; + private static JFrame frame; + private JComboBox inputDevicesComboBox = null; private JComboBox outputDevicesComboBox = null; private JButton decodeBtn = null; @@ -45,142 +50,199 @@ public class GuiApp extends JPanel implements ActionListener { public static void main(String[] args) { javax.swing.SwingUtilities.invokeLater(new Runnable() { - public void run() { - createAndShowGUI(); - } - }); + public void run() { + createAndShowGUI(); + } + }); } - + public GuiApp() { - super(new BorderLayout()); - - JPanel northPanel = new JPanel(); // **** to hold buttons - JPanel southPanel = new JPanel(); // **** to hold buttons - - List lst = Soundcard.getInputDevicesLst(); - String[] inputDeviceArray = new String[lst.size()]; - for (int i = 0; i < inputDeviceArray.length; i++) { - inputDeviceArray[i] = lst.get(i); + super(new BorderLayout()); + + JPanel northPanel = new JPanel(); // **** to hold buttons + JPanel southPanel = new JPanel(); // **** to hold buttons + + List lst = Soundcard.getInputDevicesLst(); + String[] inputDeviceArray = new String[lst.size()]; + for (int i = 0; i < inputDeviceArray.length; i++) { + inputDeviceArray[i] = lst.get(i).getName(); + textArea.setText(textArea.getText()+"Input: "+lst.get(i).getName()+" | "+lst.get(i).getDescription()+"\n"); + } - inputDevicesComboBox = new JComboBox(inputDeviceArray); - northPanel.add(inputDevicesComboBox); - - lst = Soundcard.getOutputDevicesLst(); - String[] outputDeviceArray = new String[lst.size()]; - for (int i = 0; i < outputDeviceArray.length; i++) { - outputDeviceArray[i] = lst.get(i); + audioIn = new JLabel("In:"); + northPanel.add(audioIn); + inputDevicesComboBox = new JComboBox(inputDeviceArray); + northPanel.add(inputDevicesComboBox); + + lst = Soundcard.getOutputDevicesLst(); + String[] outputDeviceArray = new String[lst.size()]; + for (int i = 0; i < outputDeviceArray.length; i++) { + outputDeviceArray[i] = lst.get(i).getName(); + textArea.setText(textArea.getText()+"Output: "+lst.get(i).getName()+" | "+lst.get(i).getDescription()+"\n"); + } - outputDevicesComboBox = new JComboBox(outputDeviceArray); - northPanel.add(outputDevicesComboBox); - - decodeBtn = new JButton("Open Audio Interface"); - decodeBtn.addActionListener(this); - northPanel.add(decodeBtn); - - resetBtn = new JButton("Reset"); - - - resetBtn.addActionListener(new ActionListener() { - + + textArea.setText(textArea.getText()+"=================================================================================\n"); + audioOut = new JLabel("Out:"); + northPanel.add(audioOut); + outputDevicesComboBox = new JComboBox(outputDeviceArray); + northPanel.add(outputDevicesComboBox); + + decodeBtn = new JButton("Open Audio Interface"); + decodeBtn.addActionListener(this); + northPanel.add(decodeBtn); + + resetBtn = new JButton("Reset"); + + + resetBtn.addActionListener(new ActionListener() { + @Override public void actionPerformed(ActionEvent arg0) { Soundcard.running = false; guiDecoderThread.interrupt(); inputDevicesComboBox.setEnabled(true); + outputDevicesComboBox.setEnabled(true); decodeBtn.setEnabled(true); resetBtn.setEnabled(false); + sendBtn.setEnabled(false); } }); - - resetBtn.setEnabled(false); - - inputPacketField = new JTextField(40); - callsign = new JTextField(6); - digipath = new JTextField(8); - destination = new JTextField(5); - southPanel.add(callsign); - southPanel.add(digipath); - southPanel.add(destination); - southPanel.add(inputPacketField); - northPanel.add(resetBtn); - - Soundcard.enumerate(); - - - - sendBtn = new JButton("Send Packet"); - sendBtn.addActionListener(new ActionListener() { - + + resetBtn.setEnabled(false); + + inputPacketField = new JTextField(40); + callsign = new JTextField(6); + digipath = new JTextField(8); + destination = new JTextField(5); + JLabel callsignL = new JLabel("Callsign:"); + southPanel.add(callsignL); + southPanel.add(callsign); + JLabel digipathL = new JLabel("Digipath:"); + southPanel.add(digipathL); + southPanel.add(digipath); + JLabel destL = new JLabel("Dest:"); + southPanel.add(destL); + southPanel.add(destination); + JLabel packetL = new JLabel("Packet:"); + southPanel.add(packetL); + southPanel.add(inputPacketField); + northPanel.add(resetBtn); + + Soundcard.enumerate(); + + + + sendBtn = new JButton("Send Packet"); + sendBtn.addActionListener(new ActionListener() { + @Override public void actionPerformed(ActionEvent arg0) { - + //PU2LVM-13 APRS @141244z2332.53S/04645.51W_131/003g004t067r000p000P000b10142h70Test ARISS,WIDE2-1 - - Packet packet = new Packet("APRS", + /* + * Packet packet = new Packet("APRS", "PU2LVM", new String[] {"WIDE1-1", "WIDE2-2"}, Packet.AX25_CONTROL_APRS, Packet.AX25_PROTOCOL_NO_LAYER_3, "@141244z2332.53S/04645.51W_131/003g004t067r000p000P000b10142h70Test".getBytes()); + */ + + boolean validInputs = false; + StringBuffer sbInputPacketErrors = new StringBuffer(); + if(destination.getText() != null && destination.getText().length() > 0) validInputs = true; + else { + validInputs = false; + sbInputPacketErrors.append("Invalid destination.\n"); + } + if(callsign.getText() != null && callsign.getText().length() > 0) validInputs = true; + else { + validInputs = false; + sbInputPacketErrors.append("Invalid callsign.\n"); + } + if(digipath.getText() != null && digipath.getText().length() > 0 && !digipath.getText().contains(" ")) validInputs = true; + else { + validInputs = false; + sbInputPacketErrors.append("Invalid digipath.\n"); + } + if(inputPacketField.getText() != null && inputPacketField.getText().length() > 0) validInputs = true; + else { + validInputs = false; + sbInputPacketErrors.append("Invalid packet data.\n"); + } + if(validInputs) { + Packet packet = new Packet(destination.getText(), + callsign.getText(), + digipath.getText().split(","), + Packet.AX25_CONTROL_APRS, + Packet.AX25_PROTOCOL_NO_LAYER_3, + inputPacketField.getText().getBytes()); - System.out.println(packet); - mod.prepareToTransmit(packet); - sc.transmit(); + System.out.println(packet); + mod.prepareToTransmit(packet); + sc.transmit(); + }else { + JOptionPane.showMessageDialog(frame, sbInputPacketErrors.toString()); + } } }); - southPanel.add(sendBtn); - - audioLevelLabel = new JLabel("Audio Level: "); - northPanel.add(audioLevelLabel); - - audioLevelValue = new JLabel("000"); - audioLevelValue.setForeground(Color.red); - northPanel.add(audioLevelValue); - - add(northPanel, BorderLayout.PAGE_START); - add(southPanel, BorderLayout.AFTER_LAST_LINE); - - DefaultCaret caret = (DefaultCaret) textArea.getCaret(); - caret.setUpdatePolicy(DefaultCaret.ALWAYS_UPDATE); - - JScrollPane scrollPane = new JScrollPane(textArea); - - add(scrollPane, BorderLayout.CENTER); - - setBorder(BorderFactory.createEmptyBorder(20,20,20,20)); - } - + sendBtn.setEnabled(false); + southPanel.add(sendBtn); + + audioLevelLabel = new JLabel("Audio Level: "); + northPanel.add(audioLevelLabel); + + audioLevelValue = new JLabel("000"); + audioLevelValue.setForeground(Color.red); + northPanel.add(audioLevelValue); + + add(northPanel, BorderLayout.PAGE_START); + add(southPanel, BorderLayout.AFTER_LAST_LINE); + + DefaultCaret caret = (DefaultCaret) textArea.getCaret(); + caret.setUpdatePolicy(DefaultCaret.ALWAYS_UPDATE); + + JScrollPane scrollPane = new JScrollPane(textArea); + + add(scrollPane, BorderLayout.CENTER); + + setBorder(BorderFactory.createEmptyBorder(20,20,20,20)); + } + private static void createAndShowGUI() { - //Create and set up the window. - JFrame frame = new JFrame("JavaX25 Decoder v.0.0.1"); - frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - ImageIcon img = new ImageIcon("dist/icon.png"); - frame.setIconImage(img.getImage()); - - //Create and set up the content pane. - JComponent newContentPane = new GuiApp(); - newContentPane.setOpaque(true); //content panes must be opaque - frame.setContentPane(newContentPane); - - //Display the window. - frame.pack(); - frame.setVisible(true); - } + //Create and set up the window. + frame = new JFrame(Constants.APP_NAME+" v"+Constants.APP_VERSION); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + ImageIcon img = new ImageIcon("dist/icon.png"); + frame.setIconImage(img.getImage()); + + //Create and set up the content pane. + JComponent newContentPane = new GuiApp(); + newContentPane.setOpaque(true); //content panes must be opaque + frame.setContentPane(newContentPane); + + //Display the window. + frame.pack(); + frame.setVisible(true); + } @Override public void actionPerformed(ActionEvent arg0) { - openAudionInterfaceAndDecode(); - + openAudioInterfaceAndDecode(); + } - - private void openAudionInterfaceAndDecode() { + + private void openAudioInterfaceAndDecode() { decodeBtn.setEnabled(false); inputDevicesComboBox.setEnabled(false); + outputDevicesComboBox.setEnabled(false); resetBtn.setEnabled(true); String input = (String) inputDevicesComboBox.getSelectedItem(); String output = (String) outputDevicesComboBox.getSelectedItem(); Soundcard.jTextArea = textArea; Soundcard.audioLevelValue = audioLevelValue; + sendBtn.setEnabled(true); guiDecoderThread = new GuiDecoderThread(input, output); guiDecoderThread.start(); } diff --git a/src/main/java/org/jdamico/javax25/PacketHandlerImpl.java b/src/main/java/org/jdamico/javax25/PacketHandlerImpl.java index c411836..6edc5d1 100644 --- a/src/main/java/org/jdamico/javax25/PacketHandlerImpl.java +++ b/src/main/java/org/jdamico/javax25/PacketHandlerImpl.java @@ -31,7 +31,7 @@ public class PacketHandlerImpl implements PacketHandler { public void handlePacket(byte[] bytes) { String data = Packet.format(bytes); - + Date now = new Date(); if(Soundcard.jTextArea == null) { System.out.println("Packet ====>>>>" +data); @@ -41,11 +41,10 @@ public void handlePacket(byte[] bytes) { String lines = Soundcard.jTextArea.getText(); if(lines.length() > 80000) lines = "Cleaning log...\n"; - Soundcard.jTextArea.setText(lines+data+"\n"); + Soundcard.jTextArea.setText(lines+"["+BasicHelper.getInstance().dateToString(now, Constants.DATE_TIME_FORMAT)+"] "+data+"\n"); } if(Soundcard.receivedPackedMap != null) { - Date now = new Date(); Soundcard.receivedPackedMap.put(now.getTime(), data); } diff --git a/src/main/java/org/jdamico/javax25/soundcard/Soundcard.java b/src/main/java/org/jdamico/javax25/soundcard/Soundcard.java index 430d246..d1853c6 100644 --- a/src/main/java/org/jdamico/javax25/soundcard/Soundcard.java +++ b/src/main/java/org/jdamico/javax25/soundcard/Soundcard.java @@ -119,7 +119,6 @@ public static void enumerate() { for (Mixer.Info mi: mis) { String name = mi.getName(); System.out.println(" "+mi.getName()+": "+mi.getVendor()+": "+mi.getDescription()); - System.out.println(" "+mi.getName()); Line.Info[] lis; lis = AudioSystem.getMixer(mi).getSourceLineInfo(); @@ -144,17 +143,16 @@ public static void enumerate() { } - public static List getInputDevicesLst() { - List lst = new ArrayList<>(); + public static List getInputDevicesLst() { + List lst = new ArrayList<>(); Mixer.Info[] mis = AudioSystem.getMixerInfo(); for (Mixer.Info mi: mis) { - String name = mi.getName(); Line.Info[] lis; lis = AudioSystem.getMixer(mi).getTargetLineInfo(); for (Line.Info li: lis) { if (TargetDataLine.class.equals(li.getLineClass())) { - lst.add(name); + lst.add(mi); } @@ -172,7 +170,10 @@ public void receive() { int buffer_size_in_samples = (int) Math.round(latency_ms * ((double) rate / 1000.0) / 4.0); capture_buffer = new byte[2*buffer_size_in_samples]; if (tdl==null) { - System.err.println("No sound input device, receiver exiting."); + String errMsg = "No sound input device, receiver exiting.\n"; + if(Soundcard.jTextArea != null) { + Soundcard.jTextArea.setText(Soundcard.jTextArea.getText()+errMsg); + }else System.err.println(errMsg); return; } tdl.flush(); @@ -300,20 +301,18 @@ private void openSoundOutput(String mixer) { } } - public static List getOutputDevicesLst() { - List lst = new ArrayList<>(); + public static List getOutputDevicesLst() { + List lst = new ArrayList<>(); Mixer.Info[] mis = AudioSystem.getMixerInfo(); for (Mixer.Info mi: mis) { - String name = mi.getName(); Line.Info[] lis; lis = AudioSystem.getMixer(mi).getSourceLineInfo(); for (Line.Info li: lis) { if (SourceDataLine.class.equals(li.getLineClass())) { - lst.add(name); + lst.add(mi); } - } } return lst; diff --git a/src/main/java/org/jdamico/javax25/GuiDecoderThread.java b/src/main/java/org/jdamico/javax25/threads/GuiDecoderThread.java similarity index 72% rename from src/main/java/org/jdamico/javax25/GuiDecoderThread.java rename to src/main/java/org/jdamico/javax25/threads/GuiDecoderThread.java index fd7c369..640e5d3 100644 --- a/src/main/java/org/jdamico/javax25/GuiDecoderThread.java +++ b/src/main/java/org/jdamico/javax25/threads/GuiDecoderThread.java @@ -1,7 +1,9 @@ -package org.jdamico.javax25; +package org.jdamico.javax25.threads; import java.util.Properties; +import org.jdamico.javax25.GuiApp; +import org.jdamico.javax25.PacketHandlerImpl; import org.jdamico.javax25.ax25.Afsk1200Modulator; import org.jdamico.javax25.ax25.Afsk1200MultiDemodulator; import org.jdamico.javax25.ax25.PacketDemodulator; @@ -51,11 +53,16 @@ public void run() { /*** listen for incoming packets ***/ if (input != null) { - System.out.printf("Listening for packets\n"); - //sc.openSoundInput(input); + String listeningMsg = "Listening for packets\n"; + if(Soundcard.jTextArea != null) { + Soundcard.jTextArea.setText(Soundcard.jTextArea.getText()+listeningMsg); + }else System.out.printf(listeningMsg); GuiApp.sc.receive(); }else { - System.err.println("Input is null!"); + String errMsg = "Input is null!"; + if(Soundcard.jTextArea != null) { + Soundcard.jTextArea.setText(Soundcard.jTextArea.getText()+errMsg); + }else System.err.println(errMsg); } }