Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 3485f6d

Browse files
committedMar 29, 2019·
Add serial monitor send/receive encoding options
- Add "send as <encoding>" dropdown menu. - Add "receive as <encoding>" dropdown menu. Sending and receiving can be done in any encoding specified in StandardCharsets with the additional option to send comma-separated bytes and receive newline-separated bytes directly. This fixes #4452 and offers an easy implementation for issue #4632.
1 parent a87024d commit 3485f6d

File tree

4 files changed

+255
-49
lines changed

4 files changed

+255
-49
lines changed
 

‎app/src/processing/app/AbstractTextMonitor.java

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ public abstract class AbstractTextMonitor extends AbstractMonitor {
4040
protected JButton clearButton;
4141
protected JCheckBox autoscrollBox;
4242
protected JCheckBox addTimeStampBox;
43+
protected JComboBox<String> sendEncoding;
44+
protected JComboBox<String> receiveEncoding;
4345
protected JComboBox<String> lineEndings;
4446
protected JComboBox<String> serialRates;
4547

@@ -105,6 +107,44 @@ public void windowGainedFocus(WindowEvent e) {
105107
minimumSize.setSize(minimumSize.getWidth() / 3, minimumSize.getHeight());
106108
noLineEndingAlert.setMinimumSize(minimumSize);
107109

110+
String sendAs = tr("Send as") + " ";
111+
sendEncoding = new JComboBox<>(new String[] {
112+
sendAs + EncodingOption.SYSTEM_DEFAULT,
113+
sendAs + EncodingOption.BYTES,
114+
sendAs + EncodingOption.UTF_8,
115+
sendAs + EncodingOption.UTF_16,
116+
sendAs + EncodingOption.UTF_16BE,
117+
sendAs + EncodingOption.UTF_16LE,
118+
sendAs + EncodingOption.ISO_8859_1,
119+
sendAs + EncodingOption.US_ASCII});
120+
sendEncoding.addActionListener(new ActionListener() {
121+
public void actionPerformed(ActionEvent event) {
122+
PreferencesData.set("serial.send_encoding", sendEncoding.getItemAt(
123+
sendEncoding.getSelectedIndex()).substring(sendAs.length()));
124+
}
125+
});
126+
String sendEncodingStr = PreferencesData.get("serial.send_encoding");
127+
if (sendEncodingStr != null) {
128+
sendEncoding.setSelectedItem(sendAs + sendEncodingStr);
129+
}
130+
sendEncoding.setMaximumSize(sendEncoding.getMinimumSize());
131+
132+
String receiveAs = tr("Receive as") + " ";
133+
receiveEncoding = new JComboBox<>(new String[] {
134+
receiveAs + EncodingOption.SYSTEM_DEFAULT,
135+
receiveAs + EncodingOption.BYTES,
136+
receiveAs + EncodingOption.UTF_8,
137+
receiveAs + EncodingOption.UTF_16,
138+
receiveAs + EncodingOption.UTF_16BE,
139+
receiveAs + EncodingOption.UTF_16LE,
140+
receiveAs + EncodingOption.ISO_8859_1,
141+
receiveAs + EncodingOption.US_ASCII});
142+
String receiveEncodingStr = PreferencesData.get("serial.receive_encoding");
143+
if (receiveEncodingStr != null) {
144+
receiveEncoding.setSelectedItem(receiveAs + receiveEncodingStr);
145+
}
146+
receiveEncoding.setMaximumSize(receiveEncoding.getMinimumSize());
147+
108148
lineEndings = new JComboBox<String>(new String[]{tr("No line ending"), tr("Newline"), tr("Carriage return"), tr("Both NL & CR")});
109149
lineEndings.addActionListener((ActionEvent event) -> {
110150
PreferencesData.setInteger("serial.line_ending", lineEndings.getSelectedIndex());
@@ -127,6 +167,10 @@ public void windowGainedFocus(WindowEvent e) {
127167
pane.add(Box.createHorizontalGlue());
128168
pane.add(noLineEndingAlert);
129169
pane.add(Box.createRigidArea(new Dimension(8, 0)));
170+
pane.add(sendEncoding);
171+
pane.add(Box.createRigidArea(new Dimension(8, 0)));
172+
pane.add(receiveEncoding);
173+
pane.add(Box.createRigidArea(new Dimension(8, 0)));
130174
pane.add(lineEndings);
131175
pane.add(Box.createRigidArea(new Dimension(8, 0)));
132176
pane.add(serialRates);
@@ -139,15 +183,16 @@ public void windowGainedFocus(WindowEvent e) {
139183
}
140184

141185
@Override
142-
protected void onEnableWindow(boolean enable)
143-
{
186+
protected void onEnableWindow(boolean enable) {
144187
textArea.setEnabled(enable);
145188
clearButton.setEnabled(enable);
146189
scrollPane.setEnabled(enable);
147190
textField.setEnabled(enable);
148191
sendButton.setEnabled(enable);
149192
autoscrollBox.setEnabled(enable);
150193
addTimeStampBox.setEnabled(enable);
194+
sendEncoding.setEnabled(enable);
195+
receiveEncoding.setEnabled(enable);
151196
lineEndings.setEnabled(enable);
152197
serialRates.setEnabled(enable);
153198
}
@@ -165,6 +210,10 @@ public void onSerialRateChange(ActionListener listener) {
165210
serialRates.addActionListener(listener);
166211
}
167212

213+
public void onReceiveEncodingChange(ActionListener listener) {
214+
receiveEncoding.addActionListener(listener);
215+
}
216+
168217
@Override
169218
public void message(String msg) {
170219
SwingUtilities.invokeLater(() -> updateTextArea(msg));
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package processing.app;
2+
3+
import java.nio.charset.Charset;
4+
import java.nio.charset.StandardCharsets;
5+
6+
/**
7+
* Represents the encoding option for decoding or encoding bytes that are
8+
* read from or written to a stream.
9+
*/
10+
public enum EncodingOption {
11+
12+
/**
13+
* The system default character set as returned by
14+
* {@link Charset#defaultCharset()}.
15+
*/
16+
SYSTEM_DEFAULT(Charset.defaultCharset()),
17+
18+
/**
19+
* Comma separated unsigned byte representation.
20+
*/
21+
BYTES(null),
22+
23+
UTF_8(StandardCharsets.UTF_8),
24+
UTF_16(StandardCharsets.UTF_16),
25+
UTF_16BE(StandardCharsets.UTF_16BE),
26+
UTF_16LE(StandardCharsets.UTF_16LE),
27+
ISO_8859_1(StandardCharsets.ISO_8859_1),
28+
US_ASCII(StandardCharsets.US_ASCII);
29+
30+
private final Charset charset;
31+
32+
private EncodingOption(Charset charset) {
33+
this.charset = charset;
34+
}
35+
36+
public Charset getCharset() {
37+
return this.charset;
38+
}
39+
40+
@Override
41+
public String toString() {
42+
switch (this) {
43+
case SYSTEM_DEFAULT:
44+
case BYTES:
45+
return this.name().replace('_', ' ').toLowerCase();
46+
default:
47+
return this.charset.name();
48+
}
49+
}
50+
51+
/**
52+
* Gets the {@link EncodingOption} with the given name.
53+
* The name match is case-insensitive and
54+
* whitespaces/dashes are interpreted as '_'.
55+
* @param name - The name of the {@link EncodingOption}.
56+
* @return The matching {@link EncodingOption}
57+
* or null when no such option exists.
58+
*/
59+
public static EncodingOption forName(String name) {
60+
if (name == null) {
61+
return null;
62+
}
63+
try {
64+
return EncodingOption.valueOf(
65+
name.replace(' ', '_').replace('-', '_').toUpperCase());
66+
} catch (IllegalArgumentException e) {
67+
return null;
68+
}
69+
// name = name.replace(' ', '_').replace('-', '_');
70+
// for (EncodingOption option : EncodingOption.values()) {
71+
// if (option.name().equalsIgnoreCase(name)) {
72+
// return option;
73+
// }
74+
// }
75+
// return null;
76+
}
77+
}

‎app/src/processing/app/SerialMonitor.java

Lines changed: 77 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323

2424
import java.awt.Color;
2525
import java.awt.event.ActionEvent;
26+
import java.awt.event.ActionListener;
27+
import java.nio.charset.Charset;
2628

2729
import static processing.app.I18n.tr;
2830

@@ -32,6 +34,11 @@ public class SerialMonitor extends AbstractTextMonitor {
3234
private Serial serial;
3335
private int serialRate;
3436

37+
private static final EncodingOption DEFAULT_SEND_ENCODING =
38+
EncodingOption.UTF_8;
39+
private static final EncodingOption DEFAULT_RECEIVE_ENCODING =
40+
EncodingOption.UTF_8;
41+
3542
public SerialMonitor(Base base, BoardPort port) {
3643
super(base, port);
3744

@@ -53,6 +60,18 @@ public SerialMonitor(Base base, BoardPort port) {
5360
}
5461
});
5562

63+
onReceiveEncodingChange((ActionEvent event) -> {
64+
String receiveAs = tr("Receive as") + " ";
65+
String selectedEncodingStr = receiveEncoding.getItemAt(
66+
receiveEncoding.getSelectedIndex()).substring(receiveAs.length());
67+
Charset selectedCharset =
68+
EncodingOption.forName(selectedEncodingStr).getCharset();
69+
if (serial.getCharset() != selectedCharset) {
70+
serial.resetDecoding(selectedCharset);
71+
PreferencesData.set("serial.receive_encoding", selectedEncodingStr);
72+
}
73+
});
74+
5675
onSendCommand((ActionEvent event) -> {
5776
send(textField.getText());
5877
textField.setText("");
@@ -76,11 +95,48 @@ private void send(String s) {
7695
default:
7796
break;
7897
}
79-
if ("".equals(s) && lineEndings.getSelectedIndex() == 0 && !PreferencesData.has("runtime.line.ending.alert.notified")) {
98+
if ("".equals(s) && lineEndings.getSelectedIndex() == 0
99+
&& !PreferencesData.has("runtime.line.ending.alert.notified")) {
80100
noLineEndingAlert.setForeground(Color.RED);
81101
PreferencesData.set("runtime.line.ending.alert.notified", "true");
82102
}
83-
serial.write(s);
103+
EncodingOption encodingOption =
104+
EncodingOption.forName(PreferencesData.get("serial.send_encoding"));
105+
if (encodingOption == null) {
106+
encodingOption = DEFAULT_SEND_ENCODING;
107+
}
108+
Charset charSet = encodingOption.getCharset();
109+
byte[] bytes;
110+
if (charSet != null) {
111+
bytes = s.getBytes(encodingOption.getCharset());
112+
} else {
113+
switch (encodingOption) {
114+
case BYTES:
115+
String[] split = s.split(",");
116+
bytes = new byte[split.length];
117+
for (int i = 0; i < split.length; i++) {
118+
String valStr = split[i].trim();
119+
try {
120+
int val = Integer.parseInt(valStr);
121+
if (val < 0x00 || val > 0xFF) {
122+
this.message("\n[ERROR] Invalid byte value given: "
123+
+ val + ". Byte values are in range [0-255].\n");
124+
return;
125+
}
126+
bytes[i] = (byte) val;
127+
} catch (NumberFormatException e) {
128+
this.message("\n[ERROR] Invalid byte value given: " + valStr
129+
+ ". Byte values are numbers in range [0-255].\n");
130+
return;
131+
}
132+
}
133+
break;
134+
default:
135+
throw new Error(
136+
"Unsupported 'send as' encoding option: " + encodingOption);
137+
}
138+
}
139+
serial.write(bytes);
84140
}
85141
}
86142

@@ -90,10 +146,27 @@ public void open() throws Exception {
90146

91147
if (serial != null) return;
92148

93-
serial = new Serial(getBoardPort().getAddress(), serialRate) {
149+
EncodingOption encodingOption =
150+
EncodingOption.forName(PreferencesData.get("serial.receive_encoding"));
151+
if (encodingOption == null) {
152+
encodingOption = DEFAULT_RECEIVE_ENCODING;
153+
}
154+
serial = new Serial(
155+
getBoardPort().getAddress(), serialRate, encodingOption.getCharset()) {
94156
@Override
95157
protected void message(char buff[], int n) {
96-
addToUpdateBuffer(buff, n);
158+
if (serial.getCharset() == null) {
159+
if(buff.length != 0) {
160+
StringBuilder strBuilder = new StringBuilder();
161+
for (int i = 0; i < n; i++) {
162+
strBuilder.append(buff[i] & 0xFF).append("\n");
163+
}
164+
addToUpdateBuffer(
165+
strBuilder.toString().toCharArray(), strBuilder.length());
166+
}
167+
} else {
168+
addToUpdateBuffer(buff, n);
169+
}
97170
}
98171
};
99172
}

‎arduino-core/src/processing/app/Serial.java

Lines changed: 50 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333
import java.nio.charset.Charset;
3434
import java.nio.charset.CharsetDecoder;
3535
import java.nio.charset.CodingErrorAction;
36-
import java.nio.charset.StandardCharsets;
3736
import java.util.Arrays;
3837
import java.util.List;
3938

@@ -60,39 +59,28 @@ public class Serial implements SerialPortEventListener {
6059
private CharBuffer outToMessage = CharBuffer.allocate(OUT_BUFFER_CAPACITY);
6160

6261
public Serial() throws SerialException {
63-
this(PreferencesData.get("serial.port"),
64-
PreferencesData.getInteger("serial.debug_rate", 9600),
65-
PreferencesData.getNonEmpty("serial.parity", "N").charAt(0),
66-
PreferencesData.getInteger("serial.databits", 8),
67-
PreferencesData.getFloat("serial.stopbits", 1),
68-
!BaseNoGui.getBoardPreferences().getBoolean("serial.disableRTS"),
69-
!BaseNoGui.getBoardPreferences().getBoolean("serial.disableDTR"));
62+
this(PreferencesData.get("serial.port"), PreferencesData.getInteger("serial.debug_rate", 9600));
7063
}
7164

7265
public Serial(int irate) throws SerialException {
73-
this(PreferencesData.get("serial.port"), irate,
74-
PreferencesData.getNonEmpty("serial.parity", "N").charAt(0),
75-
PreferencesData.getInteger("serial.databits", 8),
76-
PreferencesData.getFloat("serial.stopbits", 1),
77-
!BaseNoGui.getBoardPreferences().getBoolean("serial.disableRTS"),
78-
!BaseNoGui.getBoardPreferences().getBoolean("serial.disableDTR"));
66+
this(PreferencesData.get("serial.port"), irate);
67+
}
68+
69+
public Serial(String iname) throws SerialException {
70+
this(iname, PreferencesData.getInteger("serial.debug_rate", 9600));
7971
}
8072

8173
public Serial(String iname, int irate) throws SerialException {
82-
this(iname, irate, PreferencesData.getNonEmpty("serial.parity", "N").charAt(0),
83-
PreferencesData.getInteger("serial.databits", 8),
84-
PreferencesData.getFloat("serial.stopbits", 1),
85-
!BaseNoGui.getBoardPreferences().getBoolean("serial.disableRTS"),
86-
!BaseNoGui.getBoardPreferences().getBoolean("serial.disableDTR"));
74+
this(iname, irate, Charset.defaultCharset());
8775
}
8876

89-
public Serial(String iname) throws SerialException {
90-
this(iname, PreferencesData.getInteger("serial.debug_rate", 9600),
91-
PreferencesData.getNonEmpty("serial.parity", "N").charAt(0),
77+
public Serial(String iname, int irate, Charset charset) throws SerialException {
78+
this(iname, irate, PreferencesData.getNonEmpty("serial.parity", "N").charAt(0),
9279
PreferencesData.getInteger("serial.databits", 8),
9380
PreferencesData.getFloat("serial.stopbits", 1),
9481
!BaseNoGui.getBoardPreferences().getBoolean("serial.disableRTS"),
95-
!BaseNoGui.getBoardPreferences().getBoolean("serial.disableDTR"));
82+
!BaseNoGui.getBoardPreferences().getBoolean("serial.disableDTR"),
83+
charset);
9684
}
9785

9886
public static boolean touchForCDCReset(String iname) throws SerialException {
@@ -116,12 +104,13 @@ public static boolean touchForCDCReset(String iname) throws SerialException {
116104
}
117105
}
118106

119-
private Serial(String iname, int irate, char iparity, int idatabits, float istopbits, boolean setRTS, boolean setDTR) throws SerialException {
107+
private Serial(String iname, int irate, char iparity, int idatabits,
108+
float istopbits, boolean setRTS, boolean setDTR, Charset charset) throws SerialException {
120109
//if (port != null) port.close();
121110
//this.parent = parent;
122111
//parent.attach(this);
123112

124-
resetDecoding(StandardCharsets.UTF_8);
113+
resetDecoding(charset);
125114

126115
int parity = SerialPort.PARITY_NONE;
127116
if (iparity == 'E') parity = SerialPort.PARITY_EVEN;
@@ -175,24 +164,32 @@ public synchronized void serialEvent(SerialPortEvent serialEvent) {
175164
if (serialEvent.isRXCHAR()) {
176165
try {
177166
byte[] buf = port.readBytes(serialEvent.getEventValue());
178-
int next = 0;
179-
while(next < buf.length) {
180-
while(next < buf.length && outToMessage.hasRemaining()) {
181-
int spaceInIn = inFromSerial.remaining();
182-
int copyNow = buf.length - next < spaceInIn ? buf.length - next : spaceInIn;
183-
inFromSerial.put(buf, next, copyNow);
184-
next += copyNow;
185-
inFromSerial.flip();
186-
bytesToStrings.decode(inFromSerial, outToMessage, false);
187-
inFromSerial.compact();
167+
if (bytesToStrings == null) {
168+
char[] chars = new char[buf.length];
169+
for (int i = 0; i < buf.length; i++) {
170+
chars[i] = (char) buf[i];
188171
}
189-
outToMessage.flip();
190-
if(outToMessage.hasRemaining()) {
191-
char[] chars = new char[outToMessage.remaining()];
192-
outToMessage.get(chars);
193-
message(chars, chars.length);
172+
message(chars, chars.length);
173+
} else {
174+
int next = 0;
175+
while(next < buf.length) {
176+
while(next < buf.length && outToMessage.hasRemaining()) {
177+
int spaceInIn = inFromSerial.remaining();
178+
int copyNow = buf.length - next < spaceInIn ? buf.length - next : spaceInIn;
179+
inFromSerial.put(buf, next, copyNow);
180+
next += copyNow;
181+
inFromSerial.flip();
182+
bytesToStrings.decode(inFromSerial, outToMessage, false);
183+
inFromSerial.compact();
184+
}
185+
outToMessage.flip();
186+
if(outToMessage.hasRemaining()) {
187+
char[] chars = new char[outToMessage.remaining()];
188+
outToMessage.get(chars);
189+
message(chars, chars.length);
190+
}
191+
outToMessage.clear();
194192
}
195-
outToMessage.clear();
196193
}
197194
} catch (SerialPortException e) {
198195
errorMessage("serialEvent", e);
@@ -264,15 +261,25 @@ public void setRTS(boolean state) {
264261

265262
/**
266263
* Reset the encoding used to convert the bytes coming in
267-
* before they are handed as Strings to {@Link #message(char[], int)}.
264+
* before they are handed as char arrays to {@Link #message(char[], int)}.
265+
* @param charset - The character set that will be used for conversion or null to not perform conversion,
266+
* in which case bytes are simply cast to chars.
268267
*/
269268
public synchronized void resetDecoding(Charset charset) {
270-
bytesToStrings = charset.newDecoder()
269+
bytesToStrings = charset == null ? null : charset.newDecoder()
271270
.onMalformedInput(CodingErrorAction.REPLACE)
272271
.onUnmappableCharacter(CodingErrorAction.REPLACE)
273272
.replaceWith("\u2e2e");
274273
}
275274

275+
/**
276+
* Get the {@link Charset} used to convert the incoming bytes to chars.
277+
* @return The {@link Charset} or null when no conversion is performed, in which case bytes are simply cast to chars.
278+
*/
279+
public Charset getCharset() {
280+
return bytesToStrings == null ? null : bytesToStrings.charset();
281+
}
282+
276283
static public List<String> list() {
277284
return Arrays.asList(SerialPortList.getPortNames());
278285
}

0 commit comments

Comments
 (0)
Please sign in to comment.