//	Generated by P Bennett: Monday, 4 October 1999 at: 05:24:32 pm
//	Com Port Handler.
//	I have released this code as a guide only.
//	It will not run without the supporting classes.
//	It was origanally written to communicate with a development prototype 
//	and runs under Slackware Linux Version 4 with Blackdown jdk1.1.7_v3 
//	Green Threads and rxtx version 3.4 with Java Comms API V2.0
//	This thread is controlled by a thread that implements a queue
//	and methods for controlling the queue and allocating the resources (modems)
//	The modem used for development was a Siemens M20 Cellular modem.
//	The remote equipment dumped its data upon connection and then hangs up. 
//	The protocol has changed somewhat now. (A Subset of HDLC)
//	I have added extra comments for your benefit.
//	It is free to use.
//	Just a quick note. I have no formal training therefor the programming techniques
//	may not be the best ones to use. However the technique I use has been developed
//	through experience and it seems to work for me.

import java.util.*;
import java.io.*;
import gnu.io.*;		//Comms Extensions

/** Class To Talk To A GSM Cellular Modem, Dial an SNU and retrieve the data.
* Uses Java Comm API 2.0
* @author P Bennett
* @version 1.0
* @see SNServerIO
* @see SNComThread
*/

public class SNComHandler extends java.lang.Thread implements
SerialPortEventListener {

private static SNComThread comThread;			// A reference back to the parent.	
private static SNLogger comLog;				// Log handler
private static SNConfig serverConfig;				// Server Configuration object
private String comName = "Nil";				// The com port name to use as in /dev/ttyS0
private boolean process = false;				// Process a request
private String requestId = "Nil";				// Request ID
private String num = "Nil";					// The phone number to dial
private boolean running = true;				// Make it go around
private CommPortIdentifier portId;				// Port Identifier
private SerialPort serialPort = null;				// Serial Port object
private OutputStream outputStream = null;			// The streams
private InputStream inputStream = null;
private String errMessage = "";				// An error message
private boolean fatalErr = false;				// Big problems call the emergency team :-(
private boolean waitForInput = true;				// Wait for incoming data

/** Construct a new SNComHandler Object.. 
* For initialising the array of Com Handlers.
* @see SNComThread
*/
	public SNComHandler() {
	}

/** Construct a new SNComHandler Object.
* Created with the following parameters.
* @param cThread The parent thread.
* @param sLog The Logging object.
* @param sConfig The server config object.
* @param cName The Com Port name eg. /dev/ttyS0
* @see SNLogger
* @see SNConfig
*/
	public SNComHandler(SNComThread cThread, SNLogger sLog, SNConfig
sConfig, String cName) throws NoSuchPortException {
		this.comThread = cThread;				// Parent thread
		this.comLog = sLog;					// Log object
		this.serverConfig = sConfig;				// Config object
		this.comName = cName;				// The com port name
		portId =
(CommPortIdentifier)CommPortIdentifier.getPortIdentifier(this.comName);
 // Set up the ID
		this.comLog.log("Com Handler Initialised For " + comName);	// Log start
	}

/** Thread run method
* Call unit num, when requested and
* pass the recieved data back to
* the parent thread que.
*/
	public void run() {
//		comLog.debugInfo("Com Handler Run Method Started For " + comName);
		int resetCount = 0;				// Reset attempts
		int resetCount2 = 0;				// And again. There must be a better way  
		int resetCount3 = 0;				// And again. of doing this!!!! They are all the same. 
		while (running) {				// While we are doin it.
			if (fatalErr) {				// Big problems
				comThread.queRequest(requestId, errMessage, "Error"); 	// Tell the parent
				comLog.log(errMessage  + " " + comName + " " + num);	// Tell everyone
				num = "Nil";							// Reset some variables
				resetCount = 0;							// The error resets process
				resetCount2 = 0;						// Round we go again.
				resetCount3 = 0;
				fatalErr = false;
			}
			if (!process) {								// Nothing to do
				try {
	            			Thread.sleep(500);					// Have a sleep
        				} catch (InterruptedException e) {}
				continue;							// Round we go again.
			}
			comLog.debugInfo("**********We Are Processing***********");
			if (num.equals("Nil")) {					// Can't dial Nil!
				try {						// Just a catch never tested
	            			Thread.sleep(500);			// Have a sleep
        				} catch (InterruptedException e) {}		// Prolly does not work as intended
				continue;					// Round we go again.
			}
			comLog.debugInfo("**********Trying To Open Port***********");
			if (!openPort()) {					// Try to open port
				closePort();					// Try to close it then
				try {						
	            			Thread.sleep(500);			// Have a sleep
        				} catch (InterruptedException e) {}
				resetCount ++;					// Up the counter
				//***************** 3 goes in serverconfig ************************8
				if (resetCount > 3) {				// Check the counter
					process = false;			// We got problems
					errMessage = "Error! Could Not Open Port";
					fatalErr = true;				// We got big problems
				}
				continue;					// Round we go to sort it out
			}
			comLog.debugInfo("**********Trying To Reset Modem***********");
			if (!reset()) {						// We got the port now reset the modem
				try {
	            			Thread.sleep(500);			// Have a sleep
        				} catch (InterruptedException e) {}
				resetCount2 ++;				// Up the counter
				//***************** 3 goes in serverconfig ************************8
				if (resetCount2 > 3) {				// Check the counter
					process = false;			// We got problems
					errMessage = "Error! Could Not Reset Modem";
					fatalErr = true;				// We got big problems	
				}
				continue;					// Round we go to sort it out
			}
			comLog.debugInfo("**********Trying To Dial***********");
			if (!dial()) {						// The modem reset OK now dial
				comLog.debugInfo("**********" + errMessage + "***********");
				try {
	            			Thread.sleep(500);			// Have a sleep
        				} catch (InterruptedException e) {}
				resetCount3 ++;				// Up the count
				//***************** 3 goes in serverconfig ************************8
				if (resetCount3 > 2) {				// Check the count
					process = false;			// We got problems
					errMessage = "Error! Could Not Dial";
					fatalErr = true;				// We got big problems	
				}
				continue;					// Round we go to sort it out
			}
			int numBytes = 0;					// Number of bytes read from input
			byte[] readBuffer = new byte[20];			// Tmp Read buffer of 20 bytes
			String sReadBuff = "";					// Read Buffer
			boolean dLoop = true;					// Loop
			while (dLoop) {						// Wait for incoming data
				try {
					while (inputStream.available() > 0) {		// While there is something to read
						numBytes = inputStream.read(readBuffer);   // Get the bytes
						String tmpR = new String(readBuffer);	          // Set up a string	
						sReadBuff += tmpR.substring(0, numBytes);  // Add to read buffer
					}
				} catch (IOException e) {
					dLoop = false;					// Problems	
					process = false;				// This has never occured
				}
				if (sReadBuff.indexOf("NO CARRIER") != -1) {		// Test incoming data
					errMessage = "";				// Unit hangs up once it
					dLoop = false;					// dumps its data
				} else if (sReadBuff.indexOf("ERROR") != -1) {	// Check for error
					errMessage = "Error! Recieved Data Not Clean " + num + " " + comName;
					dLoop = false;					
				} else if (sReadBuff.length() > 5000) {			// Check for receive runnaway
					errMessage = "";
					dLoop = false;
				}
			}
			if (errMessage.equals("")) { 			// No error occured
				comThread.queRequest(requestId, sReadBuff, "Ready");	// Tell the parent the result
				comLog.log("Data Recieved " + " " + requestId + " " + num);	// Log it
				System.out.println("*********" + sReadBuff + "*********");	// Raw Debug code
			} else {
				if (!fatalErr) {				// Error
					comThread.queRequest(requestId, errMessage, "Error"); 	// Tell parent
					comLog.log(errMessage  + " " + comName + " " + num);	// Log
					System.out.println("*********" + errMessage + "*********");	// Raw debug
				}
			}
			closePort();					// Close the port
			resetCount = 0;					// Reset the variables ready for next request
			resetCount2 = 0;
			resetCount3 = 0;
			num = "Nil";
			process = false;
		}
	}

/** Open Com Port
* @return true if succesfull
*/
	private boolean openPort() {
		if (serialPort == null) {					// Set up serial port object if need be
			comLog.debugInfo("**********Open Port Routine***********");			
			try {
				serialPort = (SerialPort) portId.open("SimpleReadApp", 2000); // Open serial port
			comLog.debugInfo("**********Port Open***********");
			} catch (PortInUseException e) {
				return false;				// Hmm its in use
			}
			if (inputStream == null) {			// Set up the input stream if need be
				try {
					inputStream = serialPort.getInputStream(); 		// Get the stream
				} catch (IOException e) {
					return false;	
				}
			}
			if (outputStream == null) {			// Set up the output stream if need be
				try {
					outputStream = serialPort.getOutputStream();		// Get the stream
				} catch (IOException e) {
					return false;			
				}
			}
			try {
				serialPort.addEventListener(this);	// Add the event listener
			} catch (TooManyListenersException e) {
				return false;	
			}
			serialPort.notifyOnDataAvailable(true);	// Set the port to notify on incoming data
			//********* Maybe this goes in serverconfig maybe on a per port basis
			// Set the port parameters
			try {
serialPort.setSerialPortParams(19200, SerialPort.DATABITS_8,
SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);
			} catch (UnsupportedCommOperationException e) {
				return false;
			}
		}
		return true;		// Everything went ok
	}

/** Close Com Port. */

	private void closePort() {
		if (serialPort != null) {					// Check that the serial port is not null
			serialPort.notifyOnDataAvailable(false);	// Close down notification
			serialPort.removeEventListener();		// Remove the event listener
			if (inputStream != null) {			// Check for null
				try {
					inputStream.close();		// Close it
					inputStream = null;		// Clean up
				}
				catch (IOException e) {}
			}
			if (outputStream != null) {			// Check for null
				try {
					outputStream.close();		// Close it
					outputStream = null;		// Clean up
				}
				catch (IOException e) {}
			}
			serialPort.close();				// Close the serial port
			serialPort = null;				// Clean up
		}
	}

/** Reset The Modem 
* @return true if succesfull.
*/
	private boolean reset() {
		try {
			outputStream.write(new String("atz").getBytes());		// Send the modem atz
			outputStream.write((byte)0x0D); 				// And the other stuff
			outputStream.write((byte)0x0A); 				// <CR> <LF>
		} catch (IOException e) {
			return false;
		}
		int waitingCount = 0;							// Heres another counter
		waitForInput = true;							// We are waiting
		while (waitForInput) {							// Yes we are
			try {
	           			Thread.sleep(100);					// We have a little rest
			} catch (InterruptedException e) {}
			waitingCount ++;						// We count
			//***************** 20 goes in serverconfig ************************
			if (waitingCount > 20) {						// We have counted to much
				return false;						// Could not reset
			}
		}
		int numBytes = 0;							// Set up number of bytes read
		byte[] readBuffer = new byte[20];					// And a buffer
		String sReadBuff = "";							// And another buffer
		try {
			while (inputStream.available() > 0) {
				numBytes = inputStream.read(readBuffer);		// Read from the port
				String tmpR = new String(readBuffer);
				sReadBuff += tmpR.substring(0, numBytes); 
			}
		} catch (IOException e) {
			return false;
		}
		//********************Maybe for serverconfig
		if (sReadBuff.indexOf("OK") != -1) {			// Test for OK response from modem
			try {
	           			Thread.sleep(1000);			// We have another sleep to allow things
			} catch (InterruptedException e) {}		// to settle
			return true;
		}
		return false;						// We did not reset OK
	}

/** Dial number requested
* @return true if connected
*/
	private boolean dial() {
		try {
			comLog.debugInfo("**********" + num + "***********");
			outputStream.write(new String("atd").getBytes());		// Send atd	
			outputStream.write(num.getBytes());				// And the number
			outputStream.write((byte)0x0D);				// And the other stuff
			outputStream.write((byte)0x0A);  
		} catch (IOException e) {
			errMessage = "Error! Could Not Write To Port ";
			comLog.debugInfo("**********Error Writing***********");
			return false;							// Bad could not write
		}
		int waitingCount = 0;							// We are counting again
		waitForInput = true;							// Waiting
		while (waitForInput) {							
			try {
	           			Thread.sleep(100);					// Have a sleep
			} catch (InterruptedException e) {}
			waitingCount ++;						// Counting
			//**************** For serverconfig ************************
			if (waitingCount > 200) {					// Counted to much
				errMessage = "Error! Timed Out Waiting For Response";
				return false;						// Timed out
			}
		}
		int numBytes = 0;					// Set up for reading
		byte[] readBuffer = new byte[20];			// Youve seen it before
		String sReadBuff = "";					// The comments are getting thinner
		boolean dLoop = true;					// No need to repeat
		while (dLoop) {
			try {
				while (inputStream.available() > 0) {
					numBytes = inputStream.read(readBuffer);
					String tmpR = new String(readBuffer);
					sReadBuff += tmpR.substring(0, numBytes); 		// We read it
				}
				if (sReadBuff.indexOf("NO CARRIER") != -1) {			// Out of area
					errMessage = "Error! No Carrier";
					return false;
				} else if (sReadBuff.indexOf("BUSY") != -1) {			// Busy
					errMessage = "Error! Busy";				// Who is ringing our units
					return false;						// Maybe it is dialing out
				} else if (sReadBuff.indexOf("NO DIAL TONE") != -1) {		// Bad no signal
					errMessage = "Error! No Dial Tone";			// Were has the ariel gone
					return false;
				} else if (sReadBuff.indexOf("OK") != -1) {			// Hmm voice call no good
					errMessage = "Error! Voice Call";
					return false;
				} else if (sReadBuff.indexOf("CONNECT") != -1) {		// Ah this is what we want
					return true;						// Return true
				}
			} catch (IOException e) {
				errMessage = "Error! Could Not Read From Com Port";
				return false;			// Bad but never happened yet
			}
		}
		errMessage = "Error! Invalid Data " + sReadBuff;
		return false;				// Something has gone wrong	
	}

/** Serial Event Routine
* Set waitForInput to false when data ready
*/
	public void serialEvent(SerialPortEvent event) {
		switch(event.getEventType()) {
			case SerialPortEvent.BI:
			case SerialPortEvent.OE:
			case SerialPortEvent.FE:
			case SerialPortEvent.PE:
			case SerialPortEvent.CD:
			case SerialPortEvent.CTS:
			case SerialPortEvent.DSR:
			case SerialPortEvent.RI:
			case SerialPortEvent.OUTPUT_BUFFER_EMPTY:
				break;
			case SerialPortEvent.DATA_AVAILABLE:
				waitForInput = false;				// We got incoming
            			break;						// Thats it for serial events
        		}
    	}

/** Process Request To Call SNU.
* Request that a call be made and the data
* returned to the controlling thread.
* @see SNComThread
*/		
	public void processRequest(String req, String num) {
		requestId = req;					// Set ID
		this.num = num;					// Set the phone number
		process = true;						// Make it go
	}

/** Is Processing Call
* @return true if thread is busy
* @see SNComThread
*/
	public boolean isProcessing() {
		return process;						// Are you busy
	}

/** Is Processing This Q Id.
* @param qID The ID to test for
* @return true if the thread is processing qID
* @see SNComThread
*/
	public boolean isProcessing(String qID) {
		return	requestId.equals(qID);				// Are you busy doing this job
	}
}
/* the following was added so the code will compile.  Its not proper */
class SNLogger
{
	public void debugInfo(java.lang.String it)
	{
	}
	public void log(java.lang.String it)
	{
	}
}
class SNComThread
{
	public void queRequest(java.lang.String a, java.lang.String b, java.lang.String c)
	{
	} 
}
class SNConfig
{
}
