z80-tools/src/com/maccasoft/tools/SerialTerminal.java

737 wiersze
24 KiB
Java

/*
* Copyright (c) 2018 Marco Maccaferri and others.
* 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
*/
package com.maccasoft.tools;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.Clipboard;
import org.eclipse.swt.dnd.TextTransfer;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import com.maccasoft.tools.internal.ImageRegistry;
import jssc.SerialPort;
import jssc.SerialPortEvent;
import jssc.SerialPortEventListener;
import jssc.SerialPortException;
import jssc.SerialPortList;
import jssc.SerialPortTimeoutException;
public class SerialTerminal extends Window {
public static final String[] BAUD_RATES = new String[] {
String.valueOf(SerialPort.BAUDRATE_9600),
String.valueOf(SerialPort.BAUDRATE_19200),
String.valueOf(SerialPort.BAUDRATE_38400),
String.valueOf(SerialPort.BAUDRATE_57600),
String.valueOf(SerialPort.BAUDRATE_115200)
};
public static final byte SOH = 0x01;
public static final byte EOT = 0x04;
public static final byte ACK = 0x06;
public static final byte NAK = 0x15;
public static final byte CAN = 0x18;
public static final byte C = 0x43; // 'C' which use in XModem/CRC
Display display;
Composite container;
Terminal term;
Combo cursorKeys;
Combo comPort;
Combo baudRate;
String port;
int baud;
SerialPort serialPort;
Preferences preferences;
final SelectionAdapter comPortSelectionListener = new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
port = comPort.getText();
updateSerialPortSettings();
preferences.setSerialPort(port);
term.setFocus();
}
};
final SelectionAdapter baudRateSelectionListener = new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
baud = Integer.valueOf(baudRate.getText());
updateSerialPortSettings();
preferences.setSerialBaud(baud);
term.setFocus();
}
};
final SerialPortEventListener serialEventListener = new SerialPortEventListener() {
@Override
public void serialEvent(SerialPortEvent serialPortEvent) {
if (serialPortEvent.getEventType() == SerialPortEvent.RXCHAR) {
try {
final byte[] rx = serialPort.readBytes();
if (rx != null) {
display.syncExec(new Runnable() {
@Override
public void run() {
term.print(rx);
}
});
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
};
final KeyAdapter vt100TerminalKeyListener = new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
try {
if (!serialPort.isOpened()) {
return;
}
switch (e.keyCode) {
case SWT.ARROW_UP:
serialPort.writeByte((byte) 0x1B);
serialPort.writeByte((byte) 'A');
break;
case SWT.ARROW_DOWN:
serialPort.writeByte((byte) 0x1B);
serialPort.writeByte((byte) 'B');
break;
case SWT.ARROW_LEFT:
serialPort.writeByte((byte) 0x1B);
serialPort.writeByte((byte) 'D');
break;
case SWT.ARROW_RIGHT:
serialPort.writeByte((byte) 0x1B);
serialPort.writeByte((byte) 'C');
break;
case SWT.HOME:
serialPort.writeByte((byte) 0x1B);
serialPort.writeByte((byte) 'H');
break;
case SWT.END:
serialPort.writeByte((byte) 0x1B);
serialPort.writeByte((byte) 'K');
break;
case SWT.F1:
case SWT.F2:
case SWT.F3:
case SWT.F4:
case SWT.F5:
case SWT.F6:
case SWT.F7:
case SWT.F8:
case SWT.F9:
case SWT.F10:
serialPort.writeByte((byte) 0x1B);
serialPort.writeByte((byte) 'O');
serialPort.writeByte((byte) ('P' + (e.keyCode - SWT.F1)));
break;
default:
if (e.character != 0) {
serialPort.writeByte((byte) e.character);
}
break;
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
};
final KeyAdapter wordstarTerminalKeyListener = new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
try {
if (!serialPort.isOpened()) {
return;
}
switch (e.keyCode) {
case SWT.ARROW_UP:
serialPort.writeByte((byte) 5);
break;
case SWT.ARROW_DOWN:
serialPort.writeByte((byte) 24);
break;
case SWT.ARROW_LEFT:
if ((e.stateMask & SWT.MOD2) != 0) {
serialPort.writeByte((byte) 1);
}
else {
serialPort.writeByte((byte) 19);
}
break;
case SWT.ARROW_RIGHT:
if ((e.stateMask & SWT.MOD2) != 0) {
serialPort.writeByte((byte) 6);
}
else {
serialPort.writeByte((byte) 4);
}
break;
case SWT.INSERT:
if ((e.stateMask & SWT.MOD2) != 0) {
pasteFromClipboard();
}
else {
serialPort.writeByte((byte) 22);
}
break;
case SWT.HOME:
serialPort.writeByte((byte) 17);
serialPort.writeByte((byte) 'S');
break;
case SWT.END:
serialPort.writeByte((byte) 17);
serialPort.writeByte((byte) 'D');
break;
case SWT.PAGE_UP:
serialPort.writeByte((byte) 18);
break;
case SWT.PAGE_DOWN:
serialPort.writeByte((byte) 3);
break;
default:
if (e.character == SWT.DEL) {
serialPort.writeByte((byte) 7);
}
else if (e.character != 0) {
serialPort.writeByte((byte) e.character);
}
break;
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
};
KeyAdapter currentKeyListener;
public SerialTerminal() {
super((Shell) null);
}
@Override
protected void configureShell(Shell newShell) {
super.configureShell(newShell);
Image[] images = new Image[] {
ImageRegistry.getImageFromResources("app128.png"),
ImageRegistry.getImageFromResources("app64.png"),
ImageRegistry.getImageFromResources("app48.png"),
ImageRegistry.getImageFromResources("app32.png"),
ImageRegistry.getImageFromResources("app16.png"),
};
newShell.setImages(images);
newShell.setData(this);
}
@Override
protected Control createContents(Composite parent) {
display = parent.getDisplay();
preferences = Preferences.getInstance();
port = preferences.getSerialPort();
baud = preferences.getSerialBaud();
container = new Composite(parent, SWT.NONE);
GridLayout layout = new GridLayout(1, false);
layout.marginWidth = layout.marginHeight = 0;
container.setLayout(layout);
term = new Terminal(container);
Rectangle rect = term.getBounds();
GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true);
gridData.widthHint = rect.width;
gridData.heightHint = rect.height;
term.setLayoutData(gridData);
createBottomControls(container);
updateSerialPortSettings();
currentKeyListener = vt100TerminalKeyListener;
term.addKeyListener(currentKeyListener);
container.addDisposeListener(new DisposeListener() {
@Override
public void widgetDisposed(DisposeEvent e) {
try {
serialPort.removeEventListener();
} catch (Exception ex) {
// Do nothing
}
try {
if (serialPort.isOpened()) {
serialPort.closePort();
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
});
return container;
}
void createBottomControls(Composite parent) {
int index;
Composite container = new Composite(parent, SWT.NONE);
GridLayout layout = new GridLayout(7, false);
layout.marginBottom = layout.marginHeight;
layout.marginLeft = layout.marginRight = layout.marginWidth;
layout.marginWidth = layout.marginHeight = 0;
container.setLayout(layout);
container.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
Label label = new Label(container, SWT.NONE);
label.setText("Keys");
cursorKeys = new Combo(container, SWT.DROP_DOWN | SWT.READ_ONLY);
cursorKeys.setItems(new String[] {
"VT-100",
"WordStar"
});
cursorKeys.select(0);
cursorKeys.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
term.removeKeyListener(currentKeyListener);
switch (cursorKeys.getSelectionIndex()) {
case 0:
currentKeyListener = vt100TerminalKeyListener;
break;
case 1:
currentKeyListener = wordstarTerminalKeyListener;
break;
}
term.addKeyListener(currentKeyListener);
term.setFocus();
}
});
label = new Label(container, SWT.NONE);
label.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
label = new Label(container, SWT.NONE);
label.setText("Port");
comPort = new Combo(container, SWT.DROP_DOWN | SWT.READ_ONLY);
comPort.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false));
comPort.setItems(SerialPortList.getPortNames());
index = port != null ? comPort.indexOf(port) : -1;
if (index == -1) {
index = 0;
port = comPort.getItem(index);
}
comPort.select(index);
comPort.addSelectionListener(comPortSelectionListener);
label = new Label(container, SWT.NONE);
label.setText("Baud");
baudRate = new Combo(container, SWT.DROP_DOWN | SWT.READ_ONLY);
baudRate.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false));
baudRate.setItems(BAUD_RATES);
index = baudRate.indexOf(String.valueOf(baud));
if (index == -1) {
index = 0;
baud = Integer.valueOf(baudRate.getItem(index));
}
baudRate.select(index);
baudRate.addSelectionListener(baudRateSelectionListener);
}
void updateSerialPortSettings() {
if (serialPort == null || !serialPort.getPortName().equals(port)) {
try {
serialPort.removeEventListener();
if (serialPort.isOpened()) {
serialPort.closePort();
}
} catch (Exception e) {
}
try {
serialPort = new SerialPort(port);
serialPort.openPort();
serialPort.addEventListener(serialEventListener);
} catch (Exception e) {
e.printStackTrace();
}
}
try {
serialPort.setParams(
baud,
SerialPort.DATABITS_8,
SerialPort.STOPBITS_1,
SerialPort.PARITY_NONE,
false,
false);
} catch (Exception e) {
e.printStackTrace();
}
getShell().setText("Serial Terminal on " + port);
}
public void setFocus() {
term.setFocus();
}
public SerialPort getSerialPort() {
return serialPort;
}
void pasteFromClipboard() {
Clipboard clipboard = new Clipboard(display);
try {
final String s = (String) clipboard.getContents(TextTransfer.getInstance());
if (s != null) {
new Thread(new Runnable() {
@Override
public void run() {
try {
serialPort.writeString(s);
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
} finally {
clipboard.dispose();
}
}
public void dispose() {
getShell().dispose();
}
public void uploadPackedBinary(String name, byte[] data, IProgressMonitor monitor) throws SerialPortException, SerialPortTimeoutException {
int checksum;
String s;
try {
serialPort.removeEventListener();
} catch (SerialPortException e) {
// Do nothing
}
try {
s = preferences.getDownloadCommand();
s = s.replace("{0}", name.toUpperCase());
serialPort.writeString(s);
serialPort.writeInt(13);
flushOutput();
do {
s = readString();
} while (s.length() != 0 && !s.contains("DOWNLOAD "));
serialPort.writeString("U0\r");
flushOutput();
try {
Thread.sleep(100);
} catch (InterruptedException e1) {
// Do nothing
}
serialPort.writeString(":");
flushOutput();
try {
Thread.sleep(1000);
} catch (InterruptedException e1) {
// Do nothing
}
checksum = 0;
for (int i = 0; i < data.length; i++) {
serialPort.writeString(String.format("%02X", data[i] & 0xFF));
flushOutput();
checksum += data[i] & 0xFF;
if (i > 0 && (i & 127) == 0) {
waitDot();
if (monitor != null) {
monitor.worked(1);
}
}
if (monitor != null && monitor.isCanceled()) {
return;
}
}
serialPort.writeString(">");
flushOutput();
if ((data.length & 127) != 0) {
waitDot();
if (monitor != null) {
monitor.worked(1);
}
}
} finally {
try {
serialPort.addEventListener(serialEventListener);
} catch (SerialPortException e) {
// Do nothing
}
}
serialPort.writeString(String.format("%02X", data.length & 0xFF));
flushOutput();
serialPort.writeString(String.format("%02X", checksum & 0xFF));
flushOutput();
}
void flushOutput() throws SerialPortException {
while (serialPort.getOutputBufferBytesCount() > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// Do nothing
}
}
}
String readString() throws SerialPortException, SerialPortTimeoutException {
String s = "";
while (true) {
byte[] b = serialPort.readBytes(1, 5000);
print(b[0]);
if (b[0] == '\r') {
try {
b = serialPort.readBytes(1, 200);
print(b[0]);
} catch (SerialPortTimeoutException e) {
// Do nothing
}
break;
}
if (b[0] >= ' ') {
s = s + (char) b[0];
}
}
return s;
}
void print(byte b) {
display.syncExec(new Runnable() {
@Override
public void run() {
term.print(b);
}
});
}
void waitDot() throws SerialPortException, SerialPortTimeoutException {
while (true) {
byte[] b = serialPort.readBytes(1, 5000);
if (b != null) {
print(b[0]);
if (b[0] == '.') {
break;
}
}
}
}
public void uploadXModem(String name, byte[] data, IProgressMonitor monitor) throws SerialPortException, SerialPortTimeoutException {
String s;
try {
serialPort.removeEventListener();
} catch (SerialPortException e) {
// Do nothing
}
try {
s = preferences.getXmodemCommand();
s = s.replace("{0}", name.toUpperCase());
serialPort.writeString(s);
serialPort.writeInt(13);
flushOutput();
do {
s = readString();
} while (s.length() != 0 && !s.contains("XMODEM "));
s = "";
while (true) {
byte[] b = serialPort.readBytes(1, 15000);
print(b[0]);
if (b[0] >= ' ') {
s = s + (char) b[0];
if (s.endsWith("Overwrite (Y/N)?")) {
serialPort.writeString("Y\r");
s = "";
}
if (s.endsWith("with CRCs")) {
break;
}
}
}
boolean doCrc = false;
int packet = 1;
int errors = 0;
int i = 0;
while (i < data.length) {
try {
byte[] b = serialPort.readBytes(1, 15000);
if (b[0] == CAN) {
break;
}
if (b[0] == C) {
doCrc = true;
b[0] = NAK;
}
if (b[0] == ACK) {
i += 128;
packet++;
if (monitor != null) {
monitor.worked(1);
}
errors++;
}
if (b[0] == NAK) {
errors++;
if (errors >= 10) {
return;
}
}
if (b[0] == ACK || b[0] == NAK) {
serialPort.writeByte(SOH);
serialPort.writeByte((byte) packet);
serialPort.writeByte((byte) (packet ^ 0xFF));
int checksum = 0, crc = 0;
int x = 0;
while (x < 128 && (i + x) < data.length) {
serialPort.writeByte(data[i + x]);
checksum += data[i + x] & 0xFF;
crc = updateCrc(crc, data[i + x] & 0xFF);
x++;
}
if (x < 128) {
serialPort.writeByte((byte) 0x1A);
checksum += 0x1A;
crc = updateCrc(crc, 0x1A);
x++;
while (x < 128) {
serialPort.writeByte((byte) 0);
crc = updateCrc(crc, 0);
x++;
}
}
if (doCrc) {
serialPort.writeByte((byte) ((crc >> 8) & 0xFF));
serialPort.writeByte((byte) (crc & 0xFF));
}
else {
serialPort.writeByte((byte) checksum);
}
}
if (monitor != null && monitor.isCanceled()) {
serialPort.writeBytes(new byte[] {
CAN, CAN, CAN
});
return;
}
} catch (SerialPortTimeoutException e) {
errors++;
if (errors >= 10) {
return;
}
}
}
if (i >= data.length) {
serialPort.writeByte(EOT);
byte[] b = serialPort.readBytes(1, 5000);
if (b[0] == NAK) {
serialPort.writeByte(EOT);
}
b = serialPort.readBytes(1, 5000);
if (b[0] == NAK) {
serialPort.writeByte(EOT);
}
}
} finally {
try {
serialPort.addEventListener(serialEventListener);
} catch (SerialPortException e) {
// Do nothing
}
}
}
int updateCrc(int crc, int b) {
for (int i = 0; i < 8; i++) {
boolean bit = ((b >>> (7 - i) & 1) == 1);
boolean c15 = ((crc >>> 15 & 1) == 1);
crc <<= 1;
if (c15 ^ bit) {
crc ^= 0x1021;
}
}
return crc & 0xFFFF;
}
}