JModem - simple communications program
/*
* Copyright (c) Ian F. Darwin, http://www.darwinsys.com/, 1996-2002.
* All rights reserved. Software written by Ian F. Darwin and others.
* $Id: LICENSE,v 1.8 2004/02/09 03:33:38 ian Exp $
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* Java, the Duke mascot, and all variants of Sun's Java "steaming coffee
* cup" logo are trademarks of Sun Microsystems. Sun's, and James Gosling's,
* pioneering role in inventing and promulgating (and standardizing) the Java
* language and environment is gratefully acknowledged.
*
* The pioneering role of Dennis Ritchie and Bjarne Stroustrup, of AT&T, for
* inventing predecessor languages C and C++ is also gratefully acknowledged.
*/
import java.awt.BorderLayout;
import java.awt.Font;
import java.awt.event.KeyEvent;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.Writer;
import java.util.Enumeration;
import java.util.HashMap;
import javax.comm.CommPortIdentifier;
import javax.comm.PortInUseException;
import javax.comm.SerialPort;
import javax.comm.UnsupportedCommOperationException;
import javax.swing.ButtonGroup;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
/**
* JModem - simple communications program.
* WARNING this file was built with the NetBeans Developer IDE
* and parts of it should not be modified with a text editor.
* @author Ian F. Darwin, http://www.darwinsys.com/
* @version $Id: JModem.java,v 1.18 2004/04/11 23:50:40 ian Exp $
*/
public class JModem extends javax.swing.JFrame {
/** The Model. */
JMModel theModel;
/** The TextArea */
JTextArea theTextArea;
/** The courier font for the text areas and fields. */
protected Font plainFont;
/** The valid baud rates (actually BPS rates). */
private int[] baudot = { 9600, 19200, 38400, 57600, 115200 };
/** The types of remote systems. */
private String sysTypes[] = { "Unix", "DOS", "Other" };
private int M_RECEIVE = -1, M_SEND = +1;
private int xferDirection = M_RECEIVE;
/** Constructor */
public JModem() {
theModel = new JMModel(this);
initComponents();
finishConstructor();
pack();
}
/** This method is called from within the constructor to
* initialize the form.
* WARNING: Do NOT modify this code. The content of this method is
* always regenerated by the FormEditor.
*/
private void initComponents () {//GEN-BEGIN:initComponents
setTitle ("JModem");
addWindowListener (new java.awt.event.WindowAdapter () {
public void windowClosing (java.awt.event.WindowEvent evt) {
exitForm (evt);
}
}
);
getContentPane ().setLayout (new java.awt.BorderLayout ());
jMenuBar1 = new javax.swing.JMenuBar ();
fileMenu = new javax.swing.JMenu ();
fileMenu.setText ("File");
saveLogFileMenuItem = new javax.swing.JMenuItem ();
saveLogFileMenuItem.setText ("Save Log");
saveLogFileMenuItem.addActionListener (new java.awt.event.ActionListener () {
public void actionPerformed (java.awt.event.ActionEvent evt) {
saveLogFileMenuItemActionPerformed (evt);
}
}
);
fileMenu.add(saveLogFileMenuItem);
fileMenu.addSeparator();
exitMenuItem = new javax.swing.JMenuItem ();
exitMenuItem.setText ("Exit");
exitMenuItem.addActionListener (new java.awt.event.ActionListener () {
public void actionPerformed (java.awt.event.ActionEvent evt) {
exitMenuItemActionPerformed (evt);
}
}
);
fileMenu.add(exitMenuItem);
jMenuBar1.add(fileMenu);
helpMenu = new javax.swing.JMenu ();
helpMenu.setText ("Help");
helpAboutMenuItem = new javax.swing.JMenuItem ();
helpAboutMenuItem.setText ("Item");
helpAboutMenuItem.addActionListener (new java.awt.event.ActionListener () {
public void actionPerformed (java.awt.event.ActionEvent evt) {
helpAboutMenuItemActionPerformed (evt);
}
}
);
helpMenu.add(helpAboutMenuItem);
jMenuBar1.add(helpMenu);
setJMenuBar(jMenuBar1);
connectPanel = new javax.swing.JPanel ();
connectPanel.setLayout (new java.awt.FlowLayout ());
connectPanelLabel = new javax.swing.JLabel ();
connectPanelLabel.setText ("Connection");
connectPanelLabel.setForeground (java.awt.Color.red);
connectPanel.add (connectPanelLabel);
portsLabel = new javax.swing.JLabel ();
portsLabel.setText ("Port:");
connectPanel.add (portsLabel);
portsComboBox = new javax.swing.JComboBox ();
portsComboBox.addActionListener (new java.awt.event.ActionListener () {
public void actionPerformed (java.awt.event.ActionEvent evt) {
portsComboBoxActionPerformed (evt);
}
}
);
connectPanel.add (portsComboBox);
buadLabel = new javax.swing.JLabel ();
buadLabel.setText ("Speed");
connectPanel.add (buadLabel);
baudComboBox = new javax.swing.JComboBox ();
baudComboBox.addActionListener (new java.awt.event.ActionListener () {
public void actionPerformed (java.awt.event.ActionEvent evt) {
baudComboBoxActionPerformed (evt);
}
}
);
connectPanel.add (baudComboBox);
databitsPanel = new javax.swing.JPanel ();
databitsPanel.setPreferredSize (new java.awt.Dimension(50, 50));
databitsPanel.setMinimumSize (new java.awt.Dimension(0, 0));
databitsPanel.setLayout (new javax.swing.BoxLayout (databitsPanel, 1));
d7RadioButton = new javax.swing.JRadioButton ();
d7RadioButton.setText ("7");
databitsPanel.add (d7RadioButton);
d8RadioButton = new javax.swing.JRadioButton ();
d8RadioButton.setSelected (true);
d8RadioButton.setText ("8");
databitsPanel.add (d8RadioButton);
connectPanel.add (databitsPanel);
parityPanel = new javax.swing.JPanel ();
parityPanel.setPreferredSize (new java.awt.Dimension(50, 50));
parityPanel.setLayout (new javax.swing.BoxLayout (parityPanel, 1));
pNoneRadioButton = new javax.swing.JRadioButton ();
pNoneRadioButton.setSelected (true);
pNoneRadioButton.setText ("None");
pNoneRadioButton.addActionListener (new java.awt.event.ActionListener () {
public void actionPerformed (java.awt.event.ActionEvent evt) {
pNoneRadioButtonActionPerformed (evt);
}
}
);
parityPanel.add (pNoneRadioButton);
pEvenRadioButton = new javax.swing.JRadioButton ();
pEvenRadioButton.setText ("Even");
pEvenRadioButton.addActionListener (new java.awt.event.ActionListener () {
public void actionPerformed (java.awt.event.ActionEvent evt) {
evenRadioButtonActionPerformed (evt);
}
}
);
parityPanel.add (pEvenRadioButton);
pOddRadioButton = new javax.swing.JRadioButton ();
pOddRadioButton.setText ("Odd");
pOddRadioButton.addActionListener (new java.awt.event.ActionListener () {
public void actionPerformed (java.awt.event.ActionEvent evt) {
oddRadioButtonActionPerformed (evt);
}
}
);
parityPanel.add (pOddRadioButton);
connectPanel.add (parityPanel);
sysTypeLabel = new javax.swing.JLabel ();
sysTypeLabel.setText ("Remote:");
connectPanel.add (sysTypeLabel);
sysTypeComboBox = new javax.swing.JComboBox ();
sysTypeComboBox.addActionListener (new java.awt.event.ActionListener () {
public void actionPerformed (java.awt.event.ActionEvent evt) {
sysTypeComboBoxActionPerformed (evt);
}
}
);
connectPanel.add (sysTypeComboBox);
connectButton = new javax.swing.JButton ();
connectButton.setText ("Connect");
connectButton.addActionListener (new java.awt.event.ActionListener () {
public void actionPerformed (java.awt.event.ActionEvent evt) {
connectButtonActionPerformed (evt);
}
}
);
connectPanel.add (connectButton);
getContentPane().add(connectPanel, BorderLayout.NORTH);
xferPanel = new javax.swing.JPanel ();
xferPanel.setLayout (new java.awt.FlowLayout ());
xferPanelLabel = new javax.swing.JLabel ();
xferPanelLabel.setText ("File Transfer");
xferPanelLabel.setForeground (java.awt.Color.red);
xferPanel.add (xferPanelLabel);
jPanel6 = new javax.swing.JPanel ();
jPanel6.setLayout (new javax.swing.BoxLayout (jPanel6, 1));
sendRadioButton = new javax.swing.JRadioButton ();
sendRadioButton.setSelected (true);
sendRadioButton.setText ("Send");
sendRadioButton.addActionListener (new java.awt.event.ActionListener () {
public void actionPerformed (java.awt.event.ActionEvent evt) {
sendRadioButtonActionPerformed (evt);
}
}
);
jPanel6.add (sendRadioButton);
recvRadioButton = new javax.swing.JRadioButton ();
recvRadioButton.setText ("Receive");
recvRadioButton.addActionListener (new java.awt.event.ActionListener () {
public void actionPerformed (java.awt.event.ActionEvent evt) {
recvRadioButtonActionPerformed (evt);
}
}
);
jPanel6.add (recvRadioButton);
xferPanel.add (jPanel6);
xferFilenameLabel = new javax.swing.JLabel ();
xferFilenameLabel.setText ("Filename:");
xferPanel.add (xferFilenameLabel);
xferFileNameTF = new javax.swing.JTextField ();
xferFileNameTF.setPreferredSize (new java.awt.Dimension(100, 20));
xferPanel.add (xferFileNameTF);
jPanel7 = new javax.swing.JPanel ();
jPanel7.setLayout (new javax.swing.BoxLayout (jPanel7, 1));
xferModeTextRadioButton = new javax.swing.JRadioButton ();
xferModeTextRadioButton.setText ("Text");
jPanel7.add (xferModeTextRadioButton);
xferModeBinRadioButton = new javax.swing.JRadioButton ();
xferModeBinRadioButton.setSelected (true);
xferModeBinRadioButton.setText ("Binary");
jPanel7.add (xferModeBinRadioButton);
xferPanel.add (jPanel7);
xferButton = new javax.swing.JButton ();
xferButton.setText ("Transfer");
xferButton.addActionListener (new java.awt.event.ActionListener () {
public void actionPerformed (java.awt.event.ActionEvent evt) {
xferButtonActionPerformed (evt);
}
}
);
xferPanel.add (xferButton);
getContentPane ().add (xferPanel, BorderLayout.SOUTH);
}//GEN-END:initComponents
/** Save the session log to disk.
*/
private void saveLogFileMenuItemActionPerformed (java.awt.event.ActionEvent evt) {//GEN-FIRST:event_saveLogFileMenuItemActionPerformed
theModel.saveLogFile();
}//GEN-LAST:event_saveLogFileMenuItemActionPerformed
private void helpAboutMenuItemActionPerformed (java.awt.event.ActionEvent evt) {//GEN-FIRST:event_helpAboutMenuItemActionPerformed
note("JModem 0.0 (c) 2000 Ian F. Darwin\nhttp://www.darwinsys.com/");
}//GEN-LAST:event_helpAboutMenuItemActionPerformed
private void baudComboBoxActionPerformed (java.awt.event.ActionEvent evt) {
// Add your handling code here:
}
private void portsComboBoxActionPerformed (java.awt.event.ActionEvent evt) {//GEN-FIRST:event_portsComboBoxActionPerformed
// Add your handling code here:
}//GEN-LAST:event_portsComboBoxActionPerformed
/** A TextArea subclass with funky keypress forwarding: send to
* remote, not to local. This IS a terminal emulator, after all.
*/
class MyTextArea extends JTextArea {
MyTextArea(int r, int c) {
super(r, c);
}
/** Handle local KeyEvents: send KeyTyped to the remote. */
protected void processComponentKeyEvent(java.awt.event.KeyEvent evt) {
if (evt.getID() != KeyEvent.KEY_TYPED)
return;
// send keystrokes to remote, for processing.
// do nothing locally, to avoid user keystrokes appearing twice!
if (theModel.state != JMModel.S_CONNECTED) {
getToolkit().beep(); // or just connect()?
return;
}
char ch = evt.getKeyChar();
if (ch == '\n') { // XX if systemtype == dos
// sendChar('\r');
theModel.sendChar('\n');
return;
}
theModel.sendChar(ch);
}
}
/** Finish the initializations. */
private void finishConstructor() {
// Create the textarea with a JScrollpane wrapping it.
// Install it in Centre of the TextArea.
theTextArea = new MyTextArea(20, 80);
getContentPane().add(new JScrollPane(theTextArea), BorderLayout.CENTER);
plainFont = new Font("courier", Font.PLAIN, 13);
theTextArea.setFont(plainFont);
xferFileNameTF.setFont(plainFont);
theModel.populateComboBox();
portsComboBox.setSelectedIndex(0);
// Load up the baud rate combo box
for (int i=0; i<baudot.length; i++) {
baudComboBox.addItem(Integer.toString(baudot[i]));
}
baudComboBox.setSelectedIndex(0);
// Load up the System Type combo box
for (int i=0; i<sysTypes.length; i++) {
sysTypeComboBox.addItem(sysTypes[i]);
}
sysTypeComboBox.setSelectedIndex(0);
// put radio buttons into groups to enforce single-selection
ButtonGroup b1 = new ButtonGroup();
b1.add(d7RadioButton);
b1.add(d8RadioButton);
ButtonGroup b2 = new ButtonGroup();
b2.add(pNoneRadioButton);
b2.add(pEvenRadioButton);
b2.add(pOddRadioButton);
ButtonGroup b3 = new ButtonGroup();
b3.add(sendRadioButton);
b3.add(recvRadioButton);
ButtonGroup b4 = new ButtonGroup();
b4.add(xferModeTextRadioButton);
b4.add(xferModeBinRadioButton);
xferModeBinRadioButton.setEnabled(true);
}
private void recvRadioButtonActionPerformed (java.awt.event.ActionEvent evt) {//GEN-FIRST:event_recvRadioButtonActionPerformed
xferDirection = M_RECEIVE;
}//GEN-LAST:event_recvRadioButtonActionPerformed
private void sendRadioButtonActionPerformed (java.awt.event.ActionEvent evt) {//GEN-FIRST:event_sendRadioButtonActionPerformed
xferDirection = M_SEND;
}//GEN-LAST:event_sendRadioButtonActionPerformed
private void exitMenuItemActionPerformed (java.awt.event.ActionEvent evt) {//GEN-FIRST:event_exitMenuItemActionPerformed
System.exit(0);
}//GEN-LAST:event_exitMenuItemActionPerformed
private void sysTypeComboBoxActionPerformed (java.awt.event.ActionEvent evt) {//GEN-FIRST:event_sysTypeComboBoxActionPerformed
// Add your handling code here:
}//GEN-LAST:event_sysTypeComboBoxActionPerformed
private void pNoneRadioButtonActionPerformed (java.awt.event.ActionEvent evt) {//GEN-FIRST:event_pNoneRadioButtonActionPerformed
// Add your handling code here:
}//GEN-LAST:event_pNoneRadioButtonActionPerformed
private void oddRadioButtonActionPerformed (java.awt.event.ActionEvent evt) {//GEN-FIRST:event_oddRadioButtonActionPerformed
// Add your handling code here:
}//GEN-LAST:event_oddRadioButtonActionPerformed
private void evenRadioButtonActionPerformed (java.awt.event.ActionEvent evt) {//GEN-FIRST:event_evenRadioButtonActionPerformed
// Add your handling code here:
}//GEN-LAST:event_evenRadioButtonActionPerformed
/** This method basically toggles between Connected mode and
* disconnected mode.
*/
private void connectButtonActionPerformed (java.awt.event.ActionEvent evt) {//GEN-FIRST:event_connectButtonActionPerformed
if (theModel.state == JMModel.S_CONNECTED) {
theModel.disconnect(); // calls our disconnect() if OK
} else {
theModel.connect(); // calls our connect() if OK
}
}//GEN-LAST:event_connectButtonActionPerformed
/** Show that we have connected to the serial port. */
void connect() {
connectButton.setText("Disconnect");
theTextArea.setEditable(true);
theTextArea.requestFocus();
}
/** Show that we have connected to the serial port. */
void disconnect() {
connectButton.setText("Connect");
theTextArea.setEditable(false);
}
private void xferButtonActionPerformed (java.awt.event.ActionEvent evt) {//GEN-FIRST:event_xferButtonActionPerformed
// Do the transfer, using TModem class.
theModel.xfer();
}//GEN-LAST:event_xferButtonActionPerformed
/** Exit the Application */
private void exitForm(java.awt.event.WindowEvent evt) {//GEN-FIRST:event_exitForm
System.exit (0);
}//GEN-LAST:event_exitForm
// Some of these must be package-level visibility for JMModel,
// until we re-define the interface to that class a little...
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JPanel connectPanel;
private javax.swing.JPanel xferPanel;
private javax.swing.JLabel connectPanelLabel;
private javax.swing.JLabel portsLabel;
protected javax.swing.JComboBox portsComboBox;
private javax.swing.JLabel buadLabel;
protected javax.swing.JComboBox baudComboBox;
private javax.swing.JPanel databitsPanel;
private javax.swing.JPanel parityPanel;
private javax.swing.JLabel sysTypeLabel;
private javax.swing.JComboBox sysTypeComboBox;
private javax.swing.JButton connectButton;
private javax.swing.JRadioButton d7RadioButton;
private javax.swing.JRadioButton d8RadioButton;
private javax.swing.JRadioButton pNoneRadioButton;
private javax.swing.JRadioButton pEvenRadioButton;
private javax.swing.JRadioButton pOddRadioButton;
private javax.swing.JLabel xferPanelLabel;
private javax.swing.JPanel jPanel6;
private javax.swing.JLabel xferFilenameLabel;
private javax.swing.JTextField xferFileNameTF;
private javax.swing.JPanel jPanel7;
private javax.swing.JButton xferButton;
private javax.swing.JRadioButton sendRadioButton;
private javax.swing.JRadioButton recvRadioButton;
private javax.swing.JRadioButton xferModeTextRadioButton;
private javax.swing.JRadioButton xferModeBinRadioButton;
private javax.swing.JMenuBar jMenuBar1;
private javax.swing.JMenu fileMenu;
private javax.swing.JMenu helpMenu;
private javax.swing.JMenuItem saveLogFileMenuItem;
private javax.swing.JMenuItem exitMenuItem;
private javax.swing.JMenuItem helpAboutMenuItem;
// End of variables declaration//GEN-END:variables
/** Tell if the user wants 7 or 8-bit words */
public int getDataBits() {
if (d7RadioButton.isSelected())
return 7;
if (d8RadioButton.isSelected())
return 8;
throw new IllegalStateException("No word size in radio button group");
}
/** Tell if the user wants even, odd, or no parity. */
public int getParity() {
if (pNoneRadioButton.isSelected()) return JMModel.PARITY_NONE;
if (pEvenRadioButton.isSelected()) return JMModel.PARITY_EVEN;
if (pOddRadioButton.isSelected()) return JMModel.PARITY_ODD;
throw new IllegalStateException("No parity in radio button group");
}
/** Get the filename */
public String getXferFileName() {
return xferFileNameTF.getText();
}
/** "One if by send, two if receive" */
public boolean isSend() {
if (sendRadioButton.isSelected())
return true;
if (recvRadioButton.isSelected())
return false;
throw new IllegalStateException("No send/recv set in radio button group");
}
/** Convenience routine: Show a standard-form information dialog */
void note(String message) {
JOptionPane.showMessageDialog(this, message,
"JModem Notice", JOptionPane.INFORMATION_MESSAGE);
return;
}
/** Convenience routine: Show a standard-form error dialog */
void err(String message) {
JOptionPane.showMessageDialog(this, message,
"JModem Error", JOptionPane.ERROR_MESSAGE);
return;
}
/** Main: just create and show the application class. */
public static void main(java.lang.String[] args) {
new JModem().setVisible(true);
}
}
/**
* JMModel -- Communications I/O for JModem. No GUI stuff here.
* @author Ian F. Darwin, http://www.darwinsys.com/
* @version $Id: JMModel.java,v 1.4 2004/04/11 23:50:40 ian Exp $
*/
class JMModel extends java.lang.Object {
/** The View */
JModem theGUI;
/** The javax.com.CommPort object in use */
private SerialPort thePort;
/** The input and output streams */
private InputStream serialInput;
private OutputStream serialOutput;
/** The size of the static read buffer. */
protected static final int BUFSIZE = 1024;
/** A buffer for the read listener; preallocated once. */
static byte[] buf = new byte[BUFSIZE];
/** A Thread for reading from the remote. */
protected Thread serialReadThread;
/** A file transfer program */
protected TModem xferProg;
/** The state for disconnected and connected */
static int S_DISCONNECTED = 0, S_CONNECTED = 1;
/** The state, either disconnected or connected */
int state = S_DISCONNECTED;
/** The substate settings */
static int S_INTERACT = 0, S_XFER = 1;
/** The online state, either interactive or in xfer. Used by the
* main reader thread to avoid reading data meant for the xfer program.
*/
int submode = S_INTERACT;
// Constants to hide the Comm API from our GUI.
public final static int PARITY_NONE = SerialPort.PARITY_NONE;
public final static int PARITY_EVEN = SerialPort.PARITY_EVEN;
public final static int PARITY_ODD = SerialPort.PARITY_ODD;
private int[] baudot = { 9600, 19200, 38400, 57600, 115200 };
private String sysTypes[] = { "Unix", "DOS", "Other" };
protected HashMap portsIDmap = new HashMap();
/** Constructor */
public JMModel(JModem gui) {
theGUI = gui;
}
protected String DEFAULT_LOG_FILE = "jmodemlog.txt";;
/** Use normal java.io to save the JTextArea's session log
* into a file.
*/
public void saveLogFile() {
String fileName = DEFAULT_LOG_FILE;
try {
Writer w = new FileWriter(fileName);
theGUI.theTextArea.write(w);
w.write('\r'); w.write('\n'); // in case last line is a prompt.
w.close();
} catch (IOException e) {
theGUI.err("Error saving log file:\n" + e.toString());
return;
}
theGUI.note("Session log saved to " + fileName);
}
/** Load the list of Serial Ports into the chooser.
* This code is far too chummy with the innards of class JModem.
*/
void populateComboBox() {
// get list of ports available on this particular computer,
// by calling static method in CommPortIdentifier.
Enumeration pList = CommPortIdentifier.getPortIdentifiers();
// Process the list of ports, putting serial ports into ComboBox
while (pList.hasMoreElements()) {
CommPortIdentifier cpi = (CommPortIdentifier)pList.nextElement();
if (cpi.getPortType() == CommPortIdentifier.PORT_SERIAL) {
theGUI.portsComboBox.addItem(cpi.getName());
portsIDmap.put(cpi.getName(), cpi);
}
}
}
/** Connect to the chosen serial port, and set parameters. */
void connect() {
try {
// Open the specified serial port
CommPortIdentifier cpi = (CommPortIdentifier)portsIDmap.get(
theGUI.portsComboBox.getSelectedItem());
thePort = (SerialPort)cpi.open("JModem", 15*1000);
// Set the serial port parameters.
thePort.setSerialPortParams(
baudot[theGUI.baudComboBox.getSelectedIndex()], // baud
theGUI.getDataBits() == 7 ?
SerialPort.DATABITS_7 : SerialPort.DATABITS_8,
SerialPort.STOPBITS_1, // stop bits
theGUI.getParity()); // parity
thePort.setFlowControlMode(SerialPort.FLOWCONTROL_RTSCTS_IN &
SerialPort.FLOWCONTROL_RTSCTS_OUT);
} catch (PortInUseException pue) {
theGUI.err("Port in use: close other app, or use different port.");
return;
} catch (UnsupportedCommOperationException uoe) {
theGUI.err("Unsupported options error: try different settings");
return;
}
// Similar to "raw" mode: return when 1 or more chars available.
try {
thePort.enableReceiveThreshold(1);
if (!thePort.isReceiveThresholdEnabled()) {
theGUI.err("Could not set receive threshold");
disconnect();
return;
}
thePort.setInputBufferSize(buf.length);
} catch (UnsupportedCommOperationException ev) {
theGUI.err("Unable to set receive threshold in Comm API; port unusable.");
disconnect();
return;
}
// Get the streams
try {
serialInput = thePort.getInputStream();
} catch (IOException e) {
theGUI.err("Error getting input stream:\n" + e.toString());
return;
}
try {
serialOutput = thePort.getOutputStream();
} catch (IOException e) {
theGUI.err("Error getting output stream:\n" + e.toString());
return;
}
// Now that we're all set, create a Thread to read data from the remote
serialReadThread = new Thread(new Runnable() {
int nbytes = buf.length;
public void run() {
do {
try {
// If the xfer program is running, stay out of its way.
if (submode == S_XFER) {
delay(1000);
continue;
}
nbytes = serialInput.read(buf, 0, buf.length);
} catch (IOException ev) {
theGUI.err("Error reading from remote:\n" + ev.toString());
return;
}
// XXX need an appendChar() method in MyTextArea
String tmp = new String(buf, 0, nbytes);
theGUI.theTextArea.append(tmp);
theGUI.theTextArea.setCaretPosition(
theGUI.theTextArea.getText().length());
} while (serialInput != null);
}
});
serialReadThread.start();
// Finally, tell rest of program, and user, that we're online.
state = S_CONNECTED;
theGUI.connect();
}
/** Break our connection to the serial port. */
void disconnect() {
// Tell java.io we are done with the input and output
try {
serialReadThread.stop(); // IGNORE DEPRECATION WARNINGS; the Java
// API still lacks a reliable termination method for Threads
// that are blocked on e.g., local disk reads.
serialInput.close();
serialOutput.close();
serialOutput = null;
} catch (IOException e) {
theGUI.err("IO Exception closing port:\n" + e.toString());
}
// Tell javax.comm we are done with the port.
thePort.removeEventListener();
thePort.close();
// Discard TModem object, if present.
xferProg = null;
// Tell rest of program we are no longer online.
state = S_DISCONNECTED;
theGUI.disconnect();
}
/** Convenience routine, due to useless InterruptedException */
public void delay(long milliseconds) {
try {
Thread.sleep(milliseconds);
} catch (InterruptedException e) {
// can't happen
}
}
/** Send one character to the remote */
void sendChar(char ch) {
if (state != S_CONNECTED)
return;
// System.err.println("--> " + ch);
try {
serialOutput.write(ch);
} catch (IOException e) {
theGUI.err("Output error on remote:\n" + e.toString() +
"\nClosing connection.");
disconnect();
}
}
/** Send a String of characters to the remote. */
private void sendString(String s) {
if (state != S_CONNECTED)
return;
try {
serialOutput.write(s.getBytes());
} catch (IOException e) {
theGUI.err("Output error on remote:\n" + e.toString() +
"\nClosing connection.");
disconnect();
}
}
/** Do one complete file transfer, using TModem */
public void xfer() {
if (state != S_CONNECTED) {
theGUI.err("Must be connected to do file transfers");
return;
}
if (xferProg == null) {
xferProg = new TModem(serialInput, serialOutput,
new PrintWriter(System.out)); // xerProg discarded in disconnect()
}
String fileName = theGUI.getXferFileName();
if (fileName.length() == 0) {
theGUI.err("Filename must be given");
return;
}
// Do the transfer! If we are sending, send a "tmodem -r" to
// the other side; if receiving, send "tmodem -s" to ask it
// to send the file.
try {
if (theGUI.isSend()) {
if (!new File(fileName).canRead()) {
theGUI.err("Can't read file " + fileName + ".");
return;
}
// Other end must "r"eceive what we send.
sendString("tmodem -r " + fileName + "\r\n");
delay(500); // let command echo back to us
submode = S_XFER;
xferProg.send(fileName);
} else {
// Other end must send for us to receive.
sendString("tmodem -s " + fileName + "\r\n");
delay(500); // let command echo back to us
submode = S_XFER;
xferProg.receive(fileName);
}
} catch (InterruptedException e) {
theGUI.err("Timeout");
return;
} catch (IOException e) {
theGUI.err("IO Exception in transfer:\n" + e);
return;
} catch (Exception ev) {
theGUI.err("Protocol failure:\n" + ev);
return;
} finally {
submode = S_INTERACT;
}
theGUI.note("File Transfer completed");
}
}
/**
* a tiny version of Ward Christensen's MODEM program for UNIX.
* Written ~ 1980 by Andrew Scott Beals. Last revised 1982.
* A.D. 2000 - dragged from the archives for use in Java Cookbook.
*
* @author C version by Andrew Scott Beals, sjobrg.andy%mit-oz@mit-mc.arpa.
* @author Java version by Ian F. Darwin, ian@darwinsys.com
* $Id: TModem.java,v 1.8 2000/03/02 03:40:50 ian Exp $
*/
class TModem {
protected final byte CPMEOF = 26; /* control/z */
protected final int MAXERRORS = 10; /* max times to retry one block */
protected final int SECSIZE = 128; /* cpm sector, transmission block */
protected final int SENTIMOUT = 30; /* timeout time in send */
protected final int SLEEP = 30; /* timeout time in recv */
/* Protocol characters used */
protected final byte SOH = 1; /* Start Of Header */
protected final byte EOT = 4; /* End Of Transmission */
protected final byte ACK = 6; /* ACKnowlege */
protected final byte NAK = 0x15; /* Negative AcKnowlege */
protected InputStream inStream;
protected OutputStream outStream;
protected PrintWriter errStream;
/** Construct a TModem */
public TModem(InputStream is, OutputStream os, PrintWriter errs) {
inStream = is;
outStream = os;
errStream = errs;
}
/** Construct a TModem with default files (stdin and stdout). */
public TModem() {
inStream = System.in;
outStream = System.out;
errStream = new PrintWriter(System.err);
}
/** A main program, for direct invocation. */
public static void main(String[] argv) throws
IOException, InterruptedException {
/* argc must == 2, i.e., `java TModem -s filename' */
if (argv.length != 2)
usage();
if (argv[0].charAt(0) != '-')
usage();
TModem tm = new TModem();
tm.setStandalone(true);
boolean OK = false;
switch (argv[0].charAt(1)){
case 'r':
OK = tm.receive(argv[1]);
break;
case 's':
OK = tm.send(argv[1]);
break;
default:
usage();
}
System.out.print(OK?"Done OK":"Failed");
System.exit(0);
}
/* give user minimal usage message */
protected static void usage()
{
System.err.println("usage: TModem -r/-s file");
// not errStream, not die(), since this is static.
System.exit(1);
}
/** If we're in a standalone app it is OK to System.exit() */
protected boolean standalone = false;
public void setStandalone(boolean is) {
standalone = is;
}
public boolean isStandalone() {
return standalone;
}
/** A flag used to communicate with inner class IOTimer */
protected boolean gotChar;
/** An inner class to provide a read timeout for alarms. */
class IOTimer extends Thread {
String message;
long milliseconds;
/** Construct an IO Timer */
IOTimer(long sec, String mesg) {
milliseconds = 1000 * sec;
message = mesg;
}
public void run() {
try {
Thread.sleep(milliseconds);
} catch (InterruptedException e) {
// can't happen
}
/** Implement the timer */
if (!gotChar)
errStream.println("Timed out waiting for " + message);
die(1);
}
}
/*
* send a file to the remote
*/
public boolean send(String tfile) throws IOException, InterruptedException
{
char checksum, index, blocknumber, errorcount;
byte character;
byte[] sector = new byte[SECSIZE];
int nbytes;
DataInputStream foo;
foo = new DataInputStream(new FileInputStream(tfile));
errStream.println( "file open, ready to send");
errorcount = 0;
blocknumber = 1;
// The C version uses "alarm()", a UNIX-only system call,
// to detect if the read times out. Here we do detect it
// by using a Thread, the IOTimer class defined above.
gotChar = false;
new IOTimer(SENTIMOUT, "NAK to start send").start();
do {
character = getchar();
gotChar = true;
if (character != NAK && errorcount < MAXERRORS)
++errorcount;
} while (character != NAK && errorcount < MAXERRORS);
errStream.println( "transmission beginning");
if (errorcount == MAXERRORS) {
xerror();
}
while ((nbytes=inStream.read(sector))!=0) {
if (nbytes<SECSIZE)
sector[nbytes]=CPMEOF;
errorcount = 0;
while (errorcount < MAXERRORS) {
errStream.println( "{" + blocknumber + "} ");
putchar(SOH); /* here is our header */
putchar(blocknumber); /* the block number */
putchar(~blocknumber); /* & its complement */
checksum = 0;
for (index = 0; index < SECSIZE; index++) {
putchar(sector[index]);
checksum += sector[index];
}
putchar(checksum); /* tell our checksum */
if (getchar() != ACK)
++errorcount;
else
break;
}
if (errorcount == MAXERRORS)
xerror();
++blocknumber;
}
boolean isAck = false;
while (!isAck) {
putchar(EOT);
isAck = getchar() == ACK;
}
errStream.println( "Transmission complete.");
return true;
}
/*
* receive a file from the remote
*/
public boolean receive(String tfile) throws IOException, InterruptedException
{
char checksum, index, blocknumber, errorcount;
byte character;
byte[] sector = new byte[SECSIZE];
DataOutputStream foo;
foo = new DataOutputStream(new FileOutputStream(tfile));
System.out.println("you have " + SLEEP + " seconds...");
/* wait for the user or remote to get his act together */
gotChar = false;
new IOTimer(SLEEP, "receive from remote").start();
errStream.println("Starting receive...");
putchar(NAK);
errorcount = 0;
blocknumber = 1;
rxLoop:
do {
character = getchar();
gotChar = true;
if (character != EOT) {
try {
byte not_ch;
if (character != SOH) {
errStream.println( "Not SOH");
if (++errorcount < MAXERRORS)
continue rxLoop;
else
xerror();
}
character = getchar();
not_ch = (byte)(~getchar());
errStream.println( "[" + character + "] ");
if (character != not_ch) {
errStream.println( "Blockcounts not ~");
++errorcount;
continue rxLoop;
}
if (character != blocknumber) {
errStream.println( "Wrong blocknumber");
++errorcount;
continue rxLoop;
}
checksum = 0;
for (index = 0; index < SECSIZE; index++) {
sector[index] = getchar();
checksum += sector[index];
}
if (checksum != getchar()) {
errStream.println( "Bad checksum");
errorcount++;
continue rxLoop;
}
putchar(ACK);
blocknumber++;
try {
foo.write(sector);
} catch (IOException e) {
errStream.println("write failed, blocknumber " + blocknumber);
}
} finally {
if (errorcount != 0)
putchar(NAK);
}
}
} while (character != EOT);
foo.close();
putchar(ACK); /* tell the other end we accepted his EOT */
putchar(ACK);
putchar(ACK);
errStream.println("Receive Completed.");
return true;
}
protected byte getchar() throws IOException {
return (byte)inStream.read();
}
protected void putchar(int c) throws IOException {
outStream.write(c);
}
protected void xerror()
{
errStream.println("too many errors...aborting");
die(1);
}
protected void die(int how)
{
if (standalone)
System.exit(how);
else
System.out.println(("Error code " + how));
}
}
Related examples in the same category