/******************************************************************************* * Copyright (c) 2007-2010, G. Weirich and Elexis * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * G. Weirich - initial implementation * *******************************************************************************/ package ch.elexis.core.ui.importer.div.rs232; import gnu.io.CommPortIdentifier; import gnu.io.NoSuchPortException; import gnu.io.PortInUseException; import gnu.io.SerialPort; import gnu.io.SerialPortEvent; import gnu.io.UnsupportedCommOperationException; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Enumeration; import java.util.TooManyListenersException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.jface.dialogs.ProgressMonitorDialog; import org.eclipse.jface.operation.IRunnableWithProgress; import org.eclipse.swt.widgets.Shell; import ch.elexis.core.ui.UiDesk; import ch.rgw.io.FileTool; import ch.rgw.tools.ExHandler; public abstract class AbstractConnection implements PortEventListener { private static final String simulate = null; // "c:/abx.txt"; protected final StringBuilder sbFrame = new StringBuilder(); protected final StringBuilder sbLine = new StringBuilder(); protected int frameStart, frameEnd, overhang, checksumBytes; protected final ComPortListener listener; protected long endTime; protected int timeToWait; private int timeout; private boolean adjustEndTime; private boolean closed = false; private static byte lineSeparator; private CommPortIdentifier portId; private SerialPort sPort; private boolean bOpen; protected OutputStream os; private InputStream is; private final String myPort; private final String[] mySettings; private final String name; private int state; private Thread watchdogThread; public interface ComPortListener { public void gotData(AbstractConnection conn, byte[] bytes); public void gotBreak(AbstractConnection conn); public void timeout(); public void cancelled(); public void closed(); } public AbstractConnection(final String portName, final String port, final String settings, final ComPortListener l){ listener = l; myPort = port; mySettings = settings.split(","); //$NON-NLS-1$ name = portName; } public String connect(){ SerialParameters sp = new SerialParameters(); sp.setPortName(myPort); sp.setBaudRate(mySettings[0]); sp.setDatabits(mySettings[1]); sp.setParity(mySettings[2]); sp.setStopbits(mySettings[3]); if (mySettings.length >= 5 && mySettings[4] != null) sp.setFlowControlIn(mySettings[4]); if (mySettings.length >= 6 && mySettings[5] != null) sp.setFlowControlOut(mySettings[5]); try { if (simulate != null) { final AbstractConnection mine = this; new Thread(new Runnable() { public void run(){ try { Thread.sleep(1000); final String in = FileTool.readTextFile(new File(simulate)) .replaceAll("\\r\\n", "\r"); //$NON-NLS-1$ //$NON-NLS-2$ listener.gotData(mine, in.getBytes()); } catch (Exception ex) { } } }).start(); } else { sbLine.setLength(0); openConnection(sp); } return null; } catch (Exception ex) { ExHandler.handle(ex); return ex.getMessage(); } } /** * Attempts to open a serial connection and streams using the parameters in the SerialParameters * object. If it is unsuccesfull at any step it returns the port to a closed state, throws a * <code>SerialConnectionException</code>, and returns. * * Gives a timeout of 30 seconds on the portOpen to allow other applications to reliquish the * port if have it open and no longer need it. */ private void openConnection(final SerialParameters parameters) throws SerialConnectionException{ // Obtain a CommPortIdentifier object for the port you want to open. try { portId = CommPortIdentifier.getPortIdentifier(parameters.getPortName()); } catch (NoSuchPortException e) { String msg = e.getMessage(); if (msg == null || msg.length() == 0) { msg = e.getClass().getSimpleName(); } throw new SerialConnectionException(msg); } // Open the port represented by the CommPortIdentifier object. Give // the open call a timeout of 1 seconds try { sPort = (SerialPort) portId.open(name, 1000); } catch (PortInUseException e) { throw new SerialConnectionException(Messages.AbstractConnection_ComPortInUse); } // Set the parameters of the connection. If they won't set, close the // port before throwing an exception. try { setConnectionParameters(parameters); } catch (SerialConnectionException e) { sPort.close(); throw e; } // Open the input and output streams for the connection. If they won't // open, close the port before throwing an exception. try { os = sPort.getOutputStream(); is = sPort.getInputStream(); } catch (IOException e) { sPort.close(); throw new SerialConnectionException("Error opening i/o streams"); //$NON-NLS-1$ } // Add this object as an event listener for the serial port. try { sPort.addEventListener(this); } catch (TooManyListenersException e) { sPort.close(); throw new SerialConnectionException("too many listeners added"); //$NON-NLS-1$ } // Set notifyOnDataAvailable to true to allow event driven input. sPort.notifyOnDataAvailable(true); // Set notifyOnBreakInterrup to allow event driven break handling. sPort.notifyOnBreakInterrupt(true); // Set receive timeout to allow breaking out of polling loop during // input handling. try { sPort.enableReceiveTimeout(30); } catch (UnsupportedCommOperationException e) { throw new SerialConnectionException("Unsupported Com operation"); //$NON-NLS-1$ } bOpen = true; } /** * Sets the connection parameters to the setting in the parameters object. If set fails return * the parameters object to origional settings and throw exception. */ public void setConnectionParameters(final SerialParameters parameters) throws SerialConnectionException{ // Save state of parameters before trying a set. int oldBaudRate = sPort.getBaudRate(); int oldDatabits = sPort.getDataBits(); int oldStopbits = sPort.getStopBits(); int oldParity = sPort.getParity(); // Set connection parameters, if set fails return parameters object // to original state. try { sPort.setSerialPortParams(parameters.getBaudRate(), parameters.getDatabits(), parameters.getStopbits(), parameters.getParity()); } catch (UnsupportedCommOperationException e) { parameters.setBaudRate(oldBaudRate); parameters.setDatabits(oldDatabits); parameters.setStopbits(oldStopbits); parameters.setParity(oldParity); throw new SerialConnectionException("Unsupported parameter"); //$NON-NLS-1$ } // Set flow control. try { sPort .setFlowControlMode(parameters.getFlowControlIn() | parameters.getFlowControlOut()); } catch (UnsupportedCommOperationException e) { throw new SerialConnectionException("Unsupported flow control"); //$NON-NLS-1$ } } /** * Wait for a frame of the device to be sent. Ignores all input until a start byte is found. * collects all bytes from that point until an end byte was received or the timeout happened. * * @param start * character defining the start of a frame * @param end * character singalling end of frame * @param following * number of bytes after end to wait for (e.g. checksum) * @param timeout * number of seconds to wait for a frame to complete before givng up */ public synchronized void awaitFrame(final Shell shell, final String text, final int start, final int end, final int following, final int timeout, final boolean background, final boolean adjustEndTime){ frameStart = start; frameEnd = end; overhang = following; this.timeout = timeout; this.adjustEndTime = adjustEndTime; endTime = System.currentTimeMillis() + (timeout * 1000); if (background) { watchdogThread = new Thread(new BackgroundWatchdog()); } else { watchdogThread = new Thread(new MonitoredWatchdog(shell, text)); } timeToWait = timeout; checksumBytes = overhang; watchdogThread.start(); } /** * Handles SerialPortEvents. The two types of SerialPortEvents that this program is registered * to listen for are DATA_AVAILABLE and BI. During DATA_AVAILABLE the port buffer is read until * it is drained, when no more data is available and 30ms has passed the method returns. When a * BI event occurs the words BREAK RECEIVED are written to the messageAreaIn. */ public void serialEvent(final SerialPortEvent e){ if (adjustEndTime) { endTime = System.currentTimeMillis() + (timeout * 1000); } if (e.getEventType() == SerialPortEvent.BI) { breakInterrupt(state); } else { try { serialEvent(this.state, is, e); } catch (Exception ex) { ExHandler.handle(ex); } } } public abstract void serialEvent(final int state, final InputStream inputStream, final SerialPortEvent e) throws IOException; public void breakInterrupt(final int state){ this.watchdogThread.interrupt(); listener.gotBreak(this); } public void close(){ closed = true; if ((watchdogThread != null) && watchdogThread.isAlive()) { watchdogThread.interrupt(); } // avoid rxtx-deadlock when called from an EventListener new Thread(new Runnable() { public void run(){ try { Thread.sleep(500); sPort.close(); bOpen = false; } catch (Exception ex) { } } }).start(); } /** * Reports the open status of the port. * * @return true if port is open, false if port is closed. */ public boolean isOpen(){ return bOpen; } /** * Send a one second break signal. */ public void sendBreak(){ if (sPort != null) { sPort.sendBreak(1000); } else { ExHandler.handle(new Throwable("sPort is null")); } } public boolean send(final String data){ return send(data.getBytes()); } public boolean send(final byte[] bytes){ try { os.write(bytes); os.flush(); return true; } catch (Exception ex) { ExHandler.handle(ex); return false; } } @SuppressWarnings("unchecked") public static String[] getComPorts(){ Enumeration<CommPortIdentifier> ports = CommPortIdentifier.getPortIdentifiers(); ArrayList<String> p = new ArrayList<String>(); while (ports.hasMoreElements()) { CommPortIdentifier port = ports.nextElement(); p.add(port.getName()); } return p.toArray(new String[0]); } class BackgroundWatchdog implements Runnable { public void run(){ while (System.currentTimeMillis() < endTime && !closed) { try { Thread.sleep(1000); // 1s. } catch (InterruptedException ex) { return; } } if (closed) { listener.closed(); } else { listener.timeout(); } } } class MonitoredWatchdog implements Runnable { final Shell shell; final String text; public MonitoredWatchdog(Shell shell, String text){ super(); this.shell = shell; this.text = text; } public void run(){ final IRunnableWithProgress runnableWithProgress = new IRunnableWithProgress() { private int count = 0; public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException{ monitor.setTaskName(Messages.AbstractConnection_PleaseWait); while (!monitor.isCanceled() && System.currentTimeMillis() < endTime && !closed) { if (count == 160) { monitor.beginTask(text, 100); count = 0; } if (monitor.isCanceled()) { monitor.done(); return; } monitor.worked(1); count++; Thread.sleep(10); // 0.001s. } if (monitor.isCanceled()) { listener.cancelled(); } else if (closed) { listener.closed(); } else { listener.timeout(); } monitor.done(); } }; Thread monitorDialogThread = new Thread() { public void run(){ ProgressMonitorDialog dialog = new ProgressMonitorDialog(shell); try { dialog.run(true, true, runnableWithProgress); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } } }; UiDesk.getDisplay().asyncExec(monitorDialogThread); } } protected void interruptWatchdog(){ this.watchdogThread.interrupt(); } public byte getLineSeparator(){ return lineSeparator; } public void setState(int state){ this.state = state; } public int getState(){ return this.state; } }