com.embeddedunveiled.serial.SerialComXModem Maven / Gradle / Ivy
/**
 * Author : Rishi Gupta
 * 
 * This file is part of 'serial communication manager' library.
 *
 * The 'serial communication manager' is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by the Free Software 
 * Foundation, either version 3 of the License, or (at your option) any later version.
 *
 * The 'serial communication manager' is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 
 * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with serial communication manager. If not, see  .
 */
package com.embeddedunveiled.serial;
import java.io.File;
import java.io.FileInputStream;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
/**
 *TODO JAVADOC
 */
public final class SerialComXModem {
	private static final byte SOH = 0x01; // Start of header character
	private static final byte EOT = 0x04; // End-of-transmission character
	private static final byte ACK = 0x06; // Acknowledge byte character
	private static final byte NAK = 0x15; // Negative-acknowledge character
	private static final byte SUB = 0x1A; // Substitute/CTRL+Z
	private SerialComManager scm = null;
	private long handle = 0;
	private File fileToProcess = null;
	private int blockNumber = -1;
	private byte[] block = new byte[132];       // 132 bytes xmodem block/packet
	private BufferedInputStream inStream = null;
	private BufferedOutputStream outStream = null;
	private boolean noMoreData = false;
	/**
	 * The constructor, joins instance of this class to the instance of scm.
	 * 
	 * @param scm SerialComManager instance associated with this handle
	 * @param handle of the port on which file is to be sent
	 * @param fileToProcess File instance representing file to be sent
	 */
	public SerialComXModem(SerialComManager scm, long handle, File fileToProcess) {
		this.scm = scm;
		this.handle = handle;
		this.fileToProcess = fileToProcess;
	}
	/**
	 * For internal use only.
	 * Represents actions to execute in state machine to implement xmodem protocol for sending files.
	 */
	public boolean sendFileX() throws SecurityException, IOException, SerialComException {
		// Finite state machine
		final int CONNECT = 0;
		final int BEGINSEND = 1;
		final int WAITACK = 2;
		final int RESEND = 3;
		final int SENDNEXT = 4;
		final int ENDTX = 5;
		final int ABORT = 6;
		boolean nakReceived = false;
		boolean eotAckReceptionTimerInitialized = false;
		String errMsg = null;
		int retryCount = 0;
		int state = -1;
		byte[] data = null;
		long responseWaitTimeOut = 0;
		long eotAckWaitTimeOutValue = 0;
		inStream = new BufferedInputStream(new FileInputStream(fileToProcess));
		state = CONNECT;
		while(true) {
			switch(state) {
			case CONNECT:
				responseWaitTimeOut = System.currentTimeMillis() + 60000;
				while(nakReceived != true) {
					try {
						data = scm.readBytes(handle);
					} catch (SerialComException exp) {
						inStream.close();
						throw exp;
					}
					if(data.length > 0) {
						/* Instead of purging receive buffer and then waiting for NAK, receive all data because
						 * this approach might be faster. The other side might have opened first time and may 
						 * have flushed garbage data. So receive buffer may contain garbage + NAK character. */
						for(int x=0; x < data.length; x++) {
							if(NAK == data[x]) {
								nakReceived = true;
								state = BEGINSEND;
								break;
							}
						}
					}else {
						try {
							Thread.sleep(800);  // delay before next attempt to check NAK arrival
						} catch (InterruptedException e) {
						}
						// abort if timedout while waiting for NAK character
						if((nakReceived != true) && (System.currentTimeMillis() >= responseWaitTimeOut)) {
							errMsg = SerialComErrorMapper.ERR_TIMEOUT_RECEIVER_CONNECT;
							state = ABORT;
							break;
						}
					}
				}
				break;
			case BEGINSEND:
				blockNumber = 1; // Block numbering starts with 1 for the first block sent, not 0.
				assembleBlock();
				try {
					scm.writeBytes(handle, block);
				} catch (SerialComException exp) {
					inStream.close();
					throw exp;
				}
				state = WAITACK;
				break;
			case RESEND:
				if(retryCount > 10) {
					errMsg = SerialComErrorMapper.ERR_MAX_RETRY_REACHED;
					state = ABORT;
					break;
				}
				try {
					scm.writeBytes(handle, block);
				} catch (SerialComException exp) {
					inStream.close();
					throw exp;
				}
				state = WAITACK;
				break;
			case WAITACK:
				responseWaitTimeOut = System.currentTimeMillis() + 60000; // 1 minute
				while(true) {
					// delay before next attempt to read from serial port
					try {
						if(noMoreData != true) {
							Thread.sleep(150);
						}else {
							Thread.sleep(1500);
						}
					} catch (InterruptedException e) {
					}
					// try to read data from serial port
					try {
						data = scm.readBytes(handle);
					} catch (SerialComException exp) {
						inStream.close();
						throw exp;
					}
					/* if data received process it. if long timeout occurred abort otherwise retry reading from serial port.
					 * if nothing received at all abort. */
					if(data.length > 0) {
						break;
					}else {
						if(noMoreData == true) {
							state = ENDTX;
							break;
						}
						if(System.currentTimeMillis() >= responseWaitTimeOut) {
							if(noMoreData == true) {
								errMsg = SerialComErrorMapper.ERR_TIMEOUT_ACKNOWLEDGE_EOT;
							}else {
								errMsg = SerialComErrorMapper.ERR_TIMEOUT_ACKNOWLEDGE_BLOCK;
							}
							state = ABORT;
							break;
						}
					}
				}
				if((state != ABORT) && (state != ENDTX)) {
					if(noMoreData != true) {
						if(data[0] == ACK) {
							state = SENDNEXT;
						}else if(data[0] == NAK) {
							retryCount++;
							state = RESEND;
						}else{
							errMsg = SerialComErrorMapper.ERR_KNOWN_ERROR_OCCURED;
							state = ABORT;
						}
					}else {
						if(data[0] == ACK) {
							inStream.close();
							return true; // successfully sent file, let's go back home happily
						}else{
							if(System.currentTimeMillis() >= eotAckWaitTimeOutValue) {
								errMsg = SerialComErrorMapper.ERR_TIMEOUT_ACKNOWLEDGE_EOT;
								state = ABORT;
							}else {
								state = ENDTX;
							}
						}
					}
				}
				break;
			case SENDNEXT:
				retryCount = 0;
				blockNumber++;
				assembleBlock();
				if(noMoreData == true) {
					state = ENDTX;
					break;
				}
				try {
					scm.writeBytes(handle, block);
				} catch (SerialComException exp) {
					inStream.close();
					throw exp;
				}
				state = WAITACK;
				break;
			case ENDTX:
				if(eotAckReceptionTimerInitialized != true) {
					eotAckWaitTimeOutValue = System.currentTimeMillis() + 60000; // 1 minute
					eotAckReceptionTimerInitialized = true;
				}
				try {
					scm.writeSingleByte(handle, EOT);
				} catch (SerialComException exp) {
					inStream.close();
					throw exp;
				}
				state = WAITACK;
				break;
			case ABORT:
				/* if ioexception occurs, control will not reach here instead exception would have been
				 * thrown already. */
				inStream.close();
				throw new SerialComTimeOutException("sendFile()", errMsg);
			}
		}
	}
	// prepares xmodem block <255-blk #><--128 data bytes-->
	private void assembleBlock() throws IOException {
		int data = 0;
		int x = 0;
		int blockChecksum = 0;
		if(blockNumber > 0xFF) {
			blockNumber = 0x00;
		}
		block[0] = SOH;
		block[1] = (byte) blockNumber;
		block[2] = (byte) ~blockNumber;
		for(x=x+3; x<128+4; x++) {
			data = inStream.read();
			if(data < 0) {
				if(x != 3) {
					// assembling last block with padding
					for(x=x+0; x<128+4; x++) {
						block[x] = SUB;
					}
				}else {
					noMoreData = true;
					return;
				}
			}else {
				block[x] = (byte) data;
			}
		}
		for(x=3; x<131; x++) {
			blockChecksum = (byte)blockChecksum + block[x];
		}
		block[131] = (byte) (blockChecksum % 256);
	}
	/**
	 * For internal use only.
	 * Represents actions to execute in state machine to implement xmodem protocol for receiving files.
	 * 
	 * @throws SerialComException
	 * @throws FileNotFoundException 
	 */
	public boolean receiveFileX() throws SerialComException, FileNotFoundException, IOException {
		// Finite state machine
		final int CONNECT = 0;
		final int RECEIVEDATA = 1;
		final int VERIFY = 2;
		final int REPLY = 3;
		final int ABORT = 4;
		int a = 0;
		int x = 0;
		int z = 0;
		int retryCount = 0;
		int state = -1;
		int blockNumber = 1; //TODO CHK VALID BLK NO
		int blockChecksum = -1;
		int bufferIndex = 0;
		boolean readComplete = false;
		boolean firstBlock = false;
		boolean isCorrupted = false;
		boolean rxDone = false;
		byte[] block = new byte[132];
		byte[] data = null;
		String errMsg = null;
		outStream = new BufferedOutputStream(new FileOutputStream(fileToProcess));
		// Clear receive buffer before start
		try {
			scm.clearPortIOBuffers(handle, true, false);
		} catch (SerialComException e) {
			outStream.close();
			throw e;
		}
		state = CONNECT;
		while(true) {
			System.out.println("state : " + state);
			switch(state) {
			case CONNECT:
				if(retryCount > 10) {
					state = ABORT;
					errMsg = SerialComErrorMapper.ERR_TIMEOUT_TRANSMITTER_CONNECT;
					break;
				}
				try {
					scm.writeSingleByte(handle, NAK); // send NAK to begin tx
					firstBlock = true;
					state = RECEIVEDATA;
				} catch (SerialComException exp) {
					outStream.close();
					throw exp;
				}
				break;
			case RECEIVEDATA:
				if(firstBlock == false) {
					// TODO
				}else {
					/* If anything is not received from port with in 10 seconds (i.e. transmitter has not started sending data)
					 * go back to CONNECT state to send NAK again otherwise as data is received goto REPLY or VERIFY state
					 * based on EOT or data received. */
					for(a=0; a<34; a++) {
						try {
							Thread.sleep(300); // 300*34 = 10.2 seconds
						} catch (InterruptedException e) {
						}
						try {
							data = scm.readBytes(handle);
							System.out.println("length : " + data.length);
						} catch (SerialComException exp) {
							outStream.close();
							throw exp;
						}
						if(data.length > 0) {
							retryCount = 0;                  // reset retry count
							if(data[0] == EOT){            // transmitter sent EOT as very first data !
								state = REPLY;
								isCorrupted = false;
								rxDone = true;
								break;
							}else {
								if(data.length == 132) {   // complete block read
									for(int i=0; i < 132; i++) {
										block[i] = data[i];
									}
									state = VERIFY;
									break;
								}else {                     // partial block read
									readComplete = false;
									bufferIndex = data.length;
									for(int d=0; d < bufferIndex; d++) {
										block[d] = data[d];
									}
									while(true) {
										try {
											Thread.sleep(50);
										} catch (InterruptedException e) {
										}
										try {
											z = 0;
											data = scm.readBytes(handle);
											if(data.length > 0) {
												for(bufferIndex = bufferIndex + 0; bufferIndex < data.length; bufferIndex++) {
													block[bufferIndex] = data[z];
													z++;
												}
												if(bufferIndex > 131) {
													firstBlock = false;
													break;
												}
											}
										} catch (SerialComException exp) {
											outStream.close();
											throw exp;
										}
									}
								}
							}
						}
					}
					if(a > 33){
						state = CONNECT;
						retryCount++;
					}
				}
				break;
			case VERIFY:
				blockChecksum = 0;
				isCorrupted = false;
				// verify block number
				if(block[1] != ~block[2]){
					isCorrupted = true;
					state = REPLY;
					break;
				}
				// verify checksum
				for(x=3; x<131; x++) {
					blockChecksum = (byte)blockChecksum + block[x];
				}
				blockChecksum = (byte) (blockChecksum % 256);
				if(blockChecksum != block[131]){
					isCorrupted = true;
					state = REPLY;
				}
				state = REPLY;
				break;
			case REPLY:
				try {
					if(isCorrupted == false) {
						scm.writeSingleByte(handle, ACK);
					}else {
						scm.writeSingleByte(handle, NAK);
					}
				} catch (SerialComException exp) {
					outStream.close();
					throw exp;
				}
				if(rxDone == false) {
					state = RECEIVEDATA;
				}else {
					outStream.close();
					return true; // file reception successfully finished, let us go back home
				}
				break;
			case ABORT:
				/* if ioexception occurs, control will not reach here instead exception would have been
				 * thrown already. */
				outStream.close();
				throw new SerialComTimeOutException("receiveFile()", errMsg);
			}
		}
	}
}
      © 2015 - 2025 Weber Informatics LLC | Privacy Policy