/* -*- mode: jde; c-basic-offset: 2; indent-tabs-mode: nil -*- */
/*
PSerial - class for serial port goodness
Part of the Processing project - http://processing.org
Copyright (c) 2004 Ben Fry & Casey Reas
This library 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 2.1 of the License, or (at your option) any later version.
This library 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 this library; if not, write to the
Free Software Foundation, Inc., 59 Temple Place, Suite 330,
Boston, MA 02111-1307 USA
*/
/*
* For documentation on jssc see http://java-simple-serial-connector.googlecode.com/svn/trunk/additional_content/javadoc/0.8/index.html
*/
package io.sloeber.core.api;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Pattern;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.ServiceRegistration;
import io.sloeber.core.common.Common;
import io.sloeber.core.common.Const;
import jssc.SerialPort;
import jssc.SerialPortEvent;
import jssc.SerialPortEventListener;
import jssc.SerialPortException;
import jssc.SerialPortList;
public class Serial implements SerialPortEventListener {
// PApplet parent;
// properties can be passed in for default values
// otherwise defaults to 9600 N81
// these could be made static, which might be a solution
// for the classloading problem.. because if code ran again,
// the static class would have an object that could be closed
SerialPort port = null;
int rate;
int parity;
int databits;
// read buffer and streams
int stopbits;
boolean monitor = false;
// initial state of RTS&DTR line(ON/OFF)
// This is needed as Some boards reset when the serial port is opened with
// RTS and DTR low.
boolean dtr = true;
String portName;
private ServiceRegistration<Serial> fServiceRegistration;
private List<MessageConsumer> fConsumers;
public Serial(String iname, int irate) {
this(iname, irate, 'N', 8, 1.0f, true);
}
public Serial(String iname, int irate, boolean dtr) {
this(iname, irate, 'N', 8, 1.0f, dtr);
}
public Serial(String iname, int irate, char iparity, int idatabits, float istopbits, boolean dtr) {
this.portName = iname;
this.rate = irate;
this.dtr = dtr;
this.parity = SerialPort.PARITY_NONE;
if (iparity == 'E')
this.parity = SerialPort.PARITY_EVEN;
if (iparity == 'O')
this.parity = SerialPort.PARITY_ODD;
this.databits = idatabits;
this.stopbits = SerialPort.STOPBITS_1;
if (istopbits == 1.5f)
this.stopbits = SerialPort.STOPBITS_1_5;
if (istopbits == 2)
this.stopbits = SerialPort.STOPBITS_2;
connect();
}
/**
* General error reporting, all correlated here just in case I think of
* something slightly more intelligent to do.
*/
public static void errorMessage(String where, Throwable e) {
Common.log(new Status(IStatus.WARNING, Const.CORE_PLUGIN_ID, "Error inside Serial. " + where, e)); //$NON-NLS-1$
}
/**
* If this just hangs and never completes on Windows, it may be because the
* DLL doesn't have its exec bit set. Why the hell that'd be the case, who
* knows.
*/
public static List<String> list() {
try {
String[] portNames;
String os = System.getProperty("os.name").toLowerCase(); //$NON-NLS-1$
if (os.indexOf("mac") >= 0) { //$NON-NLS-1$
portNames = SerialPortList.getPortNames("/dev/", Pattern.compile("^cu\\..*(UART|serial|usb).*")); //$NON-NLS-1$ //$NON-NLS-2$
} else {
portNames = SerialPortList.getPortNames();
}
return new ArrayList<>(Arrays.asList(portNames));
} catch (Exception e) {
Common.log(new Status(IStatus.ERROR, Const.CORE_PLUGIN_ID,
"There is a config problem on your system.\nFor more detail see https://github.com/jantje/arduino-eclipse-plugin/issues/252", //$NON-NLS-1$
e));
List<String> ret = new ArrayList<>();
ret.add("config error:"); //$NON-NLS-1$
ret.add("see https://github.com/jantje/arduino-eclipse-plugin/issues/252"); //$NON-NLS-1$
return ret;
}
}
public void addListener(MessageConsumer consumer) {
if (this.fConsumers == null) {
this.fConsumers = new ArrayList<>();
}
this.fConsumers.add(consumer);
}
public void removeListener(MessageConsumer consumer) {
if (this.fConsumers == null)
return;
this.fConsumers.remove(consumer);
}
public void connect() {
connect(1);
}
public void connect(int maxTries) {
if (this.port == null) {
int count = 0;
while (true) {
try {
this.port = new SerialPort(this.portName);
this.port.openPort();
this.port.setParams(this.rate, this.databits, this.stopbits, this.parity, this.dtr, this.dtr);
int eventMask = SerialPort.MASK_RXCHAR | SerialPort.MASK_BREAK;
this.port.addEventListener(this, eventMask);
return;
} catch (SerialPortException e) {
// handle exception
if (++count == maxTries) {
if (SerialPortException.TYPE_PORT_BUSY.equals(e.getExceptionType())) {
Common.log(new Status(IStatus.ERROR, Const.CORE_PLUGIN_ID,
"Serial port " + this.portName //$NON-NLS-1$
+ " already in use. Try quiting any programs that may be using it", //$NON-NLS-1$
e));
} else if (SerialPortException.TYPE_PORT_NOT_FOUND.equals(e.getExceptionType())) {
Common.log(new Status(IStatus.ERROR, Const.CORE_PLUGIN_ID, "Serial port " //$NON-NLS-1$
+ this.portName
+ " not found. Did you select the right one from the project properties -> Arduino -> Arduino?", //$NON-NLS-1$
e));
} else {
Common.log(new Status(IStatus.ERROR, Const.CORE_PLUGIN_ID,
"Error opening serial port " + this.portName, e)); //$NON-NLS-1$
}
return;
}
try {
Thread.sleep(200);
} catch (InterruptedException e1) {
Common.log(new Status(IStatus.WARNING, Const.CORE_PLUGIN_ID, "Sleep failed", e1)); //$NON-NLS-1$
}
}
// If an exception was thrown, delete port variable
this.port = null;
}
}
}
public void disconnect() {
if (this.port != null) {
try {
this.port.closePort();
} catch (SerialPortException e) {
e.printStackTrace();
}
this.port = null;
}
}
public void dispose() {
notifyConsumersOfEvent("Disconnect of port " + this.port.getPortName() + " executed"); //$NON-NLS-1$ //$NON-NLS-2$
disconnect();
if (this.fServiceRegistration != null) {
this.fServiceRegistration.unregister();
}
}
public boolean IsConnected() {
return this.port != null && this.port.isOpened();
}
private void notifyConsumersOfData(byte[] message) {
if (this.fConsumers != null) {
for (MessageConsumer consumer : this.fConsumers) {
consumer.message(message);
}
}
}
private void notifyConsumersOfEvent(String message) {
if (this.fConsumers != null) {
for (MessageConsumer consumer : this.fConsumers) {
consumer.event(message);
}
}
}
public void registerService() {
this.fServiceRegistration = FrameworkUtil.getBundle(getClass()).getBundleContext().registerService(Serial.class,
this, null);
}
public void reset() {
setDTR(false);
setRTS(false);
try {
Thread.sleep(100);
} catch (InterruptedException e) {// JABA is not going to add code
}
setDTR(true);
setRTS(true);
}
@Override
public synchronized void serialEvent(SerialPortEvent serialEvent) {
switch (serialEvent.getEventType()) {
case SerialPortEvent.RXCHAR:
int bytesCount = serialEvent.getEventValue();
if (IsConnected() && bytesCount > 0) {
try {
notifyConsumersOfData(this.port.readBytes(bytesCount));
} catch (SerialPortException e) {
errorMessage("serialEvent", e); //$NON-NLS-1$
}
}
break;
case SerialPortEvent.BREAK:
errorMessage("Break detected", new Exception()); //$NON-NLS-1$
break;
default:
break;
}
}
public void setDTR(boolean state) {
if (IsConnected()) {
try {
this.port.setDTR(state);
} catch (SerialPortException e) {
e.printStackTrace();
}
}
}
public void setRTS(boolean state) {
if (IsConnected()) {
try {
this.port.setRTS(state);
} catch (SerialPortException e) {
e.printStackTrace();
}
}
}
public void setup() {// JABA is not going to add code
}
// needed to fill viewers in jfases
@Override
public String toString() {
return this.portName;
}
public void write(byte[] bytes) {
if (this.port != null) {
try {
this.port.writeBytes(bytes);
} catch (SerialPortException e) {
errorMessage("write", e); //$NON-NLS-1$
}
}
}
/**
* This will handle both ints, bytes and chars transparently.
*/
public void write(int what) { // will also cover char
if (this.port != null) {
try {
this.port.writeByte((byte) (what & 0xFF));
} catch (SerialPortException e) {
errorMessage("write", e); //$NON-NLS-1$
}
}
}
/**
* Write a String to the output. Note that this doesn't account for Unicode
* (two bytes per char), nor will it send UTF8 characters.. It assumes that
* you mean to send a byte buffer (most often the case for networking and
* serial i/o) and will only use the bottom 8 bits of each char in the
* string. (Meaning that internally it uses String.getBytes)
*
* If you want to move Unicode data, you can first convert the String to a
* byte stream in the representation of your choice (i.e. UTF8 or two-byte
* Unicode data), and send it as a byte array.
*/
public void write(String what) {
write(what.getBytes());
}
public void write(String what, String lineEnd) {
notifyConsumersOfEvent(
System.getProperty("line.separator") + ">>Send to " + this.portName + ": \"" + what + "\"<<" //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
+ System.getProperty("line.separator")); //$NON-NLS-1$
write(what.getBytes());
if (lineEnd.length() > 0) {
write(lineEnd.getBytes());
}
}
}