/*********************************************************************************
* TotalCross Software Development Kit *
* Copyright (C) 1998, 1999 Wabasoft <www.wabasoft.com> *
* Copyright (C) 2000 Matthias Ringwald *
* Copyright (C) 2000-2012 SuperWaba Ltda. *
* All Rights Reserved *
* *
* This library and virtual machine 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. *
* *
* This file is covered by the GNU LESSER GENERAL PUBLIC LICENSE VERSION 3.0 *
* A copy of this license is located in file license.txt at the root of this *
* SDK or can be downloaded here: *
* http://www.gnu.org/licenses/lgpl-3.0.txt *
* *
*********************************************************************************/
package totalcross.io.device;
import java.lang.reflect.*;
import totalcross.io.*;
import totalcross.sys.Convert;
/**
* PortConnector accesses the device ports.
* <p>
* This works on the devices and on JDK (in this case, it uses javax.comm, which must
* be installed separately)
* <p>
* When a serial port is created, an attempt is made to open the port.
* If the open attempt is successful, the port will remain open until close() is called.
* If close() is never called, the port will be closed when the object
* is garbage collected.
* <p>
*
* Here is an example showing data being written and read from a serial port:
*
* <pre>
* PortConnector port = new PortConnector(0, 9600);
* DataStream ds = new DataStream(port);
* ds.writeCString("Hello...\r\n");
* port.close();
* </pre>
* On JDK, you can define a file called serial.properties with the number and ports
* to be used:<br>
* DEFAULT=a<br>
* IRCOMM=b<br>
* SIR=c<br>
* USB=d<br>
* If the file is not found, it will use COM1, COM2, COM3, and COM4, respectively.
*/
public class PortConnector extends Stream
{
private Object portConnectorRef;
Object _receiveBuffer; // Used only on PALMOS
int _portNumber;
/** Defines the write timeout, in milliseconds. The default is 6 seconds. */
public int writeTimeout = 6000; // guich@570_67
/** Defines the read timeout, in milliseconds. The default is 6 seconds. */
public int readTimeout = 6000; // guich@570_67
/** Set to false to not let the timeout error stop the write check loop. */
public boolean stopWriteCheckOnTimeout = true; // guich@570_65
// Logical Ports used in TotalCross
// NOTE: The TotalCross VM indexes directly to these values
/** Default Port (cradle) */
public static final int DEFAULT = 0;
/** IrCOMM Port (Serial Connection on top of IrDA Stack) */
public static final int IRCOMM = 0x1000;
/** SIR Port (Physical Layer of IrDA Stack). Important! In order
* to access the SIR port on the Pocket PCs the following has to be
* UNCHECKED: Settings/Connections/Beam/Receive all incoming beams and select
* discoverable mode. Note that Palm OS OMAP-based devices do NOT support
* SIR (aka raw IR); see this: http://www.alanjmcf.me.uk/comms/infrared/IrDA%20FAQ.html
* and http://news.palmos.com/read/messages?id=146145#146145.
* Note also that there's no handshaking support on SIR, and that not all devices work
* with it. Use IRCOMM instead when possible.
*/
public static final int SIR = 0x1001;
/** USB Endpoint 2 Port */
public static final int USB = 0x1002;
/** Bluetooth: open the built-in Bluetooth discovery dialog,
* then establish a Serial Port Profile (just serial emulation across
* Bluetooth, using the RFComm BT layer) virtual serial port connection.
* Note: on WinCE, this maps to the Serial (port 0). You must explicitly
* map your BlueTooth outside the program.
*/
public static final int BLUETOOTH = 0x1003; // guich@330_34
/** Used in the constructor to define the parity */
public static final int PARITY_NONE = 0;
/** Used in the constructor to define the parity */
public static final int PARITY_EVEN = 1;
/** Used in the constructor to define the parity */
public static final int PARITY_ODD = 2;
// JDK implementation
private Object thisInputStream;
private Object thisOutputStream;
/**
* Opens a port.
*
* @param number port number. In Windows, this is the number of the COM port.
* @param baudRate baud rate
* @param bits bits per char [5 to 8]
* @param parity true for even parity, false for no parity
* @param stopBits number of stop bits
* @see #setFlowControl
* @see #PortConnector(int, int, int, int, int)
*/
public PortConnector(int number, int baudRate, int bits, boolean parity, int stopBits) throws totalcross.io.IllegalArgumentIOException, totalcross.io.IOException
{
this(number, baudRate, bits, parity?PARITY_EVEN:PARITY_NONE, stopBits);
}
/**
* Opens a port. The number passed is the number of the
* port for devices with multiple ports. Port number
* 0 defines the "default port" of a given type. For Windows CE
* and Palm OS devices, you should pass 0 as the port number to
* access the device's single serial or usb port.
* <p>
* On Windows devices, port numbers map to COM port numbers.
* For example, serial port 2 maps to "COM2:".
* <p>
* Here is an example showing how to open the serial port of a
* Palm OS device at 9600 baud with settings of 8 bits,
* no partity and one stop bit (8/N/1):
* <pre>
* PortConnector port = new PortConnector(0, 9600, 8, false, 1);
* </pre>
* Here is an example of opening serial port COM2: on a Windows device:
* <pre>
* PortConnector port = new PortConnector(2, 57600, 8, false, 1);
* </pre>
* No serial XON/XOFF flow control (commonly called software flow control)
* is used and RTS/CTS flow control (commonly called hardware flow control)
* is turn on by default on all platforms but Windows CE. The parity setting
* must be one of the constants PARITY_NONE, PARITY_EVEN, PARITY_ODD. For Palm OS,
* PARITY_ODD turns on XON/XOFF mode.
*
* @param number port number. In Windows, this is the number of the COM port. On Windows and Palm OS, you can pass
* a 4-letter that indicates the connection that will be used. For example, on Palm OS you could use <code>Convert.chars2int("rfcm")</code>
* or in Windows CE use <code>Convert.chars2int("COM4")</code>.
* @param baudRate baud rate
* @param bits bits per char [5 to 8]
* @param parity of the constants PARITY_NONE, PARITY_EVEN, PARITY_ODD. For Palm OS, PARITY_ODD turns on XonXoff mode.
* @param stopBits number of stop bits
* @see #setFlowControl
* @since SuperWaba 4.5
*/
public PortConnector(int number, int baudRate, int bits, int parity, int stopBits) throws totalcross.io.IllegalArgumentIOException, totalcross.io.IOException
{
if (bits < 5 || bits > 8)
throw new totalcross.io.IllegalArgumentIOException("bits", Convert.toString(bits));
_portNumber = number;
create(number, baudRate, bits, parity, stopBits);
}
/**
* Open a port with settings of 8 bits, no parity and 1 stop bit.
* These are the most commonly used port settings.
* @param number port number. On Windows, this is the number of the COM port.
* @param baudRate baud rate
* @see #PortConnector(int, int, int, int, int)
*/
public PortConnector(int number, int baudRate) throws totalcross.io.IllegalArgumentIOException, totalcross.io.IOException
{
this(number, baudRate, 8, PARITY_NONE, 1);
}
private void create(int number, int baudRate, int bits, int parity, int stopBits) throws totalcross.io.IOException
{
try
{
// guich@330_38: use introspection to be able to compile everything without the comm.jar
// javax.comm.CommPortIdentifier portId = javax.comm.CommPortIdentifier.getPortIdentifier(getPortName(number));
Class<?> cpi = Class.forName("javax.comm.CommPortIdentifier"); // get the class
String portName = getPortName(number);
System.out.println("Port found: "+portName);
Method mGetPortIdentifier = cpi.getMethod("getPortIdentifier", new Class[]{String.class}); // get the method getPortIdentifier(String)
Object portId = mGetPortIdentifier.invoke(null,new Object[]{portName}); // invoke the getPortIdentifier(getPortName(number)) method
// javax.comm.SerialPort port = (javax.comm.SerialPort)portId.open("TotalCross", 1000);
Method mOpen = portId.getClass().getMethod("open", new Class[]{String.class,int.class}); // get the method getPortIdentifier(String)
Object port = mOpen.invoke(portId,new Object[]{"TotalCross",new Integer(1000)}); // invoke the portId.open method
// port.setSerialPortParams(baudRate, bits, stopBits, parity?javax.comm.SerialPort.PARITY_EVEN:javax.comm.SerialPort.PARITY_NONE);
Class<?> cSerialPort = port.getClass().getSuperclass();
Method mSetSerialPortParams = cSerialPort.getMethod("setSerialPortParams", new Class[]{int.class,int.class,int.class,int.class}); // get the method setSerialPortParams
String []parities = {"PARITY_NONE","PARITY_EVEN","PARITY_ODD"};
Field parityField = cSerialPort.getField(parities[parity]);
mSetSerialPortParams.invoke(port,new Object[]{new Integer(baudRate), new Integer(bits), new Integer(stopBits), new Integer(parityField.getInt(port))});
// port.enableReceiveTimeout(100);
Method mEnableReceiveTimeout = cSerialPort.getMethod("enableReceiveTimeout", new Class[]{int.class});
mEnableReceiveTimeout.invoke(port,new Object[]{new Integer(100)});
// thisInputStream = port.getInputStream();
Method mGetInputStream = cSerialPort.getMethod("getInputStream");
thisInputStream = mGetInputStream.invoke(port);
// thisOutputStream = port.getOutputStream();
Method mGetOutputStream = cSerialPort.getMethod("getOutputStream");
thisOutputStream = mGetOutputStream.invoke(port);
portConnectorRef = port;
}
catch (java.lang.reflect.InvocationTargetException ule)
{
throw new totalcross.io.IOException(ule.getTargetException().getClass().getName()+" - "+ule.getTargetException().getMessage() + "\nThe probable cause is that the port is in use (You must kill HotSync)\nor that the win32com.dll is not accessible in your path. Please add '/TotalCross/etc/tools/commapi' to your path or add '-Djava.library.path=/TotalCross/etc/tools/commapi' to your java.exe command"); // guich@tc110_62:
}
catch (java.lang.NoClassDefFoundError e)
{
throw new java.lang.NoClassDefFoundError(e.getClass().getName()+" - "+e.getMessage() + "\nCannot find javax.comm.CommPortIdentifier. You must add the /TotalCross3/etc/tools/commapi/comm.jar file to your classpath. If you're not using Windows, then you must download the respective Comm api at http://java.sun.com/products/javacomm");
}
catch (Throwable t)
{
throw new totalcross.io.IOException(t.getClass().getName()+" - "+t.getMessage());
}
}
/**
* Closes the port.
*/
public void close() throws totalcross.io.IOException
{
if (portConnectorRef == null)
throw new totalcross.io.IOException("The port is not open");
try
{
nativeClose();
}
finally
{
portConnectorRef = null;
}
}
final private void nativeClose() throws totalcross.io.IOException
{
try
{
// ((javax.comm.SerialPort)thisPortConnector).close();
Method m = portConnectorRef.getClass().getSuperclass().getMethod("close");
m.invoke(portConnectorRef);
}
catch (Throwable e)
{
throw new totalcross.io.IOException(e.getMessage());
}
}
/**
* Turns RTS/CTS flow control (hardware flow control) on or off.
* @param on pass true to set flow control on and false to set it off
*/
final public void setFlowControl(boolean on) throws totalcross.io.IOException
{
try
{
//((javax.comm.SerialPort)thisPortConnector).setFlowControlMode(on?javax.comm.SerialPort.FLOWCONTROL_RTSCTS_IN|javax.comm.SerialPort.FLOWCONTROL_RTSCTS_OUT:javax.comm.SerialPort.FLOWCONTROL_NONE);
Field fRTSCTS_IN = portConnectorRef.getClass().getSuperclass().getField("FLOWCONTROL_RTSCTS_IN");
Field fRTSCTS_OUT = portConnectorRef.getClass().getSuperclass().getField("FLOWCONTROL_RTSCTS_OUT");
Field fNONE = portConnectorRef.getClass().getSuperclass().getField("FLOWCONTROL_NONE");
Method m = portConnectorRef.getClass().getSuperclass().getMethod("setFlowControlMode", new Class[]{int.class});
m.invoke(portConnectorRef,new Object[]{on ? new Integer(fRTSCTS_IN.getInt(portConnectorRef)|fRTSCTS_OUT.getInt(portConnectorRef)) : new Integer(fNONE.getInt(portConnectorRef))});
}
catch (Throwable t)
{
throw new totalcross.io.IOException(t.getMessage() + "\nNot Supported?");
}
}
public int readBytes(byte buf[], int start, int count) throws totalcross.io.IOException
{
if (portConnectorRef == null)
throw new totalcross.io.IOException("The port is not open");
if (buf == null)
throw new java.lang.NullPointerException("Argument 'buf' cannot have a null value.");
if (start < 0 || count < 0 || start + count > buf.length)
throw new ArrayIndexOutOfBoundsException();
if (count == 0)
return 0;
return readWriteBytes(buf, start, count, true);
}
/**
* Returns the number of bytes currently available to be read from the
* port's queue. */
final public int readCheck() throws totalcross.io.IOException
{
try
{
//return ((java.io.InputStream)thisInputStream).available();
Method m = thisInputStream.getClass().getSuperclass().getMethod("available");
Integer i = (Integer)m.invoke(thisInputStream);
return i.intValue();
}
catch (Throwable t)
{
throw new totalcross.io.IOException(t.getMessage());
}
}
/**
* Writes to the port. Returns the number of bytes written or throws an <code>IOException</code> if an error prevented the write operation from occurring. If data
* can't be written to the port and flow control is on, the write
* operation will time out and fail after approximately 2 seconds.
* @param buf the byte array to write data from
* @param start the start position in the byte array
* @param count the number of bytes to write
*/
public int writeBytes(byte buf[], int start, int count) throws totalcross.io.IOException
{
if (portConnectorRef == null)
throw new totalcross.io.IOException("The port is not open");
if (buf == null)
throw new java.lang.NullPointerException("Argument 'buf' cannot have a null value.");
if (start < 0 || count < 0 || start + count > buf.length)
throw new ArrayIndexOutOfBoundsException();
if (count == 0)
return 0;
return readWriteBytes(buf, start, count, false);
}
final private int readWriteBytes(byte buf[], int start, int count, boolean isRead) throws totalcross.io.IOException
{
if (isRead)
{
try
{
// guich@tc: now the readTimeout is a field
Method mEnableReceiveTimeout = portConnectorRef.getClass().getSuperclass().getMethod("enableReceiveTimeout", new Class[]{int.class});
mEnableReceiveTimeout.invoke(portConnectorRef,new Object[]{new Integer(readTimeout)});
int tries = readTries;
Method m = thisInputStream.getClass().getSuperclass().getMethod("read", new Class[]{byte[].class,int.class,int.class});
// guich@330_42: make sure we have read everything
int total = count;
do
{
//return ((java.io.InputStream)thisInputStream).read(buf, start, count);
Integer i = (Integer)m.invoke(thisInputStream,new Object[]{buf,new Integer(start),new Integer(count)});
int read = i.intValue();
if (read <= 0) // guich@566_2: avoid infinite loop: a timeout returns 0 bytes
break;
start += read;
count -= read;
}
while (count > 0 && tries-- > 0);
return total;
}
catch (Throwable t)
{
throw new totalcross.io.IOException(t.getMessage() + "\nNot Supported?");
}
}
else
{
try
{
// guich@330_42: make sure we have wrote everything
//((java.io.OutputStream)thisOutputStream).write(buf, start, count);
// note: write returns void
Method m = thisOutputStream.getClass().getSuperclass().getMethod("write", new Class[]{byte[].class,int.class,int.class});
m.invoke(thisOutputStream,new Object[]{buf,new Integer(start),new Integer(count)});
return count;
}
catch (Throwable t)
{
throw new totalcross.io.IOException(t.getMessage() + "\nNot Supported?");
}
}
}
// JDK implementation
private static String portDEFAULT, portIRCOMM, portSIR, portUSB;
/** Set this to a value that will be used to keep trying to read further while something is available.
* If a series of bytes is returned in very few steps, this will make the read stop after readTries has reached zero.
* @since SuperWaba 5.72
*/
public static int readTries = 10; // guich@572_8
private static void initSerial()
{
java.util.ResourceBundle serial;
try
{
serial = java.util.ResourceBundle.getBundle("serial");
portDEFAULT = serial.getString("DEFAULT");
portIRCOMM = serial.getString("IRCOMM");
portSIR = serial.getString("SIR");
portUSB = serial.getString("USB");
}
catch (java.util.MissingResourceException e)
{
System.out.println("Error: missing resource bundle: "+e.getClassName());
System.out.println("Using defaults..."); // guich@400_20
portDEFAULT="COM1";
portIRCOMM="COM2";
portSIR="COM3";
portUSB="COM4";
}
}
/**
* @return When number == Logical port number defined in totalcross.io.device.PortConnector -
* name of port assigned in serial.properties file.
* For other values - returns first port name with given number inside of it.
*/
static private String getPortName(int number)
{
if (portDEFAULT == null)
initSerial();
switch(number)
{
case PortConnector.DEFAULT: return portDEFAULT;
case PortConnector.IRCOMM: return portIRCOMM;
case PortConnector.SIR: return portSIR;
case PortConnector.USB: return portUSB;
}
try
{
//java.util.Enumeration portIds = javax.comm.CommPortIdentifier.getPortIdentifiers();
Class<?> cpi = Class.forName("javax.comm.CommPortIdentifier"); // get the class
Method mGetPortIdentifiers = cpi.getMethod("getPortIdentifiers");
java.util.Enumeration<?> portIds = (java.util.Enumeration<?>)mGetPortIdentifiers.invoke(null);
if (number == 0)
{
if (portIds.hasMoreElements())
{
// return ((javax.comm.CommPortIdentifier)portIds.nextElement()).getName();
Object oCommPortIdentifier = portIds.nextElement();
Method mGetName = oCommPortIdentifier.getClass().getMethod("getName");
String portName = (String)mGetName.invoke(oCommPortIdentifier);
return portName;
}
}
else
{
while (portIds.hasMoreElements())
{
// portName = ((javax.comm.CommPortIdentifier)portIds.nextElement()).getName();
Object oCommPortIdentifier = portIds.nextElement();
Method mGetName = oCommPortIdentifier.getClass().getMethod("getName");
String portName = (String)mGetName.invoke(oCommPortIdentifier);
if (portName.indexOf(number+'0') != -1)
return portName;
}
}
} catch (Exception ee) {ee.printStackTrace();}
return null;
}
protected void finalize()
{
try
{
close();
}
catch (totalcross.io.IOException e)
{
}
}
}