Skip to content

Commit 79fa806

Browse files
committed
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 2b11e94 commit 79fa806

File tree

4 files changed

+259
-49
lines changed

4 files changed

+259
-49
lines changed

Diff for: app/src/processing/app/AbstractTextMonitor.java

+53-2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
import java.awt.event.ActionListener;
1111
import java.awt.event.WindowAdapter;
1212
import java.awt.event.WindowEvent;
13+
import java.nio.charset.Charset;
14+
import java.nio.charset.StandardCharsets;
1315
import java.text.SimpleDateFormat;
1416
import java.util.Date;
1517
import java.util.StringTokenizer;
@@ -40,6 +42,8 @@ public abstract class AbstractTextMonitor extends AbstractMonitor {
4042
protected JButton clearButton;
4143
protected JCheckBox autoscrollBox;
4244
protected JCheckBox addTimeStampBox;
45+
protected JComboBox<String> sendEncoding;
46+
protected JComboBox<String> receiveEncoding;
4347
protected JComboBox lineEndings;
4448
protected JComboBox serialRates;
4549

@@ -103,6 +107,45 @@ public void windowGainedFocus(WindowEvent e) {
103107
minimumSize.setSize(minimumSize.getWidth() / 3, minimumSize.getHeight());
104108
noLineEndingAlert.setMinimumSize(minimumSize);
105109

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+
// sendEncoding.setForeground(pane.getBackground());
125+
}
126+
});
127+
String sendEncodingStr = PreferencesData.get("serial.send_encoding");
128+
if (sendEncodingStr != null) {
129+
sendEncoding.setSelectedItem(sendAs + sendEncodingStr);
130+
}
131+
sendEncoding.setMaximumSize(sendEncoding.getMinimumSize());
132+
133+
String receiveAs = tr("Receive as") + " ";
134+
receiveEncoding = new JComboBox<>(new String[] {
135+
receiveAs + EncodingOption.SYSTEM_DEFAULT,
136+
receiveAs + EncodingOption.BYTES,
137+
receiveAs + EncodingOption.UTF_8,
138+
receiveAs + EncodingOption.UTF_16,
139+
receiveAs + EncodingOption.UTF_16BE,
140+
receiveAs + EncodingOption.UTF_16LE,
141+
receiveAs + EncodingOption.ISO_8859_1,
142+
receiveAs + EncodingOption.US_ASCII});
143+
String receiveEncodingStr = PreferencesData.get("serial.receive_encoding");
144+
if (receiveEncodingStr != null) {
145+
receiveEncoding.setSelectedItem(receiveAs + receiveEncodingStr);
146+
}
147+
receiveEncoding.setMaximumSize(receiveEncoding.getMinimumSize());
148+
106149
lineEndings = new JComboBox(new String[]{tr("No line ending"), tr("Newline"), tr("Carriage return"), tr("Both NL & CR")});
107150
lineEndings.addActionListener(new ActionListener() {
108151
public void actionPerformed(ActionEvent event) {
@@ -121,21 +164,23 @@ public void actionPerformed(ActionEvent e) {
121164
PreferencesData.setBoolean("serial.show_timestamp", addTimeStampBox.isSelected());
122165
}
123166
});
124-
125167
lineEndings.setMaximumSize(lineEndings.getMinimumSize());
126168

127169
serialRates = new JComboBox();
128170
for (String rate : serialRateStrings) {
129171
serialRates.addItem(rate + " " + tr("baud"));
130172
}
131-
132173
serialRates.setMaximumSize(serialRates.getMinimumSize());
133174

134175
pane.add(autoscrollBox);
135176
pane.add(addTimeStampBox);
136177
pane.add(Box.createHorizontalGlue());
137178
pane.add(noLineEndingAlert);
138179
pane.add(Box.createRigidArea(new Dimension(8, 0)));
180+
pane.add(sendEncoding);
181+
pane.add(Box.createRigidArea(new Dimension(8, 0)));
182+
pane.add(receiveEncoding);
183+
pane.add(Box.createRigidArea(new Dimension(8, 0)));
139184
pane.add(lineEndings);
140185
pane.add(Box.createRigidArea(new Dimension(8, 0)));
141186
pane.add(serialRates);
@@ -154,6 +199,8 @@ protected void onEnableWindow(boolean enable)
154199
sendButton.setEnabled(enable);
155200
autoscrollBox.setEnabled(enable);
156201
addTimeStampBox.setEnabled(enable);
202+
sendEncoding.setEnabled(enable);
203+
receiveEncoding.setEnabled(enable);
157204
lineEndings.setEnabled(enable);
158205
serialRates.setEnabled(enable);
159206
}
@@ -171,6 +218,10 @@ public void onSerialRateChange(ActionListener listener) {
171218
serialRates.addActionListener(listener);
172219
}
173220

221+
public void onReceiveEncodingChange(ActionListener listener) {
222+
receiveEncoding.addActionListener(listener);
223+
}
224+
174225
public void message(String msg) {
175226
SwingUtilities.invokeLater(() -> updateTextArea(msg));
176227
}

Diff for: app/src/processing/app/EncodingOption.java

+77
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+
}

Diff for: app/src/processing/app/SerialMonitor.java

+79-4
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import java.awt.*;
2525
import java.awt.event.ActionEvent;
2626
import java.awt.event.ActionListener;
27+
import java.nio.charset.Charset;
2728

2829
import static processing.app.I18n.tr;
2930

@@ -33,6 +34,11 @@ public class SerialMonitor extends AbstractTextMonitor {
3334
private Serial serial;
3435
private int serialRate;
3536

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+
3642
public SerialMonitor(BoardPort port) {
3743
super(port);
3844

@@ -56,6 +62,21 @@ public void actionPerformed(ActionEvent event) {
5662
}
5763
});
5864

65+
onReceiveEncodingChange(new ActionListener() {
66+
@Override
67+
public void actionPerformed(ActionEvent e) {
68+
String receiveAs = tr("Receive as") + " ";
69+
String selectedEncodingStr = receiveEncoding.getItemAt(
70+
receiveEncoding.getSelectedIndex()).substring(receiveAs.length());
71+
Charset selectedCharset =
72+
EncodingOption.forName(selectedEncodingStr).getCharset();
73+
if (serial.getCharset() != selectedCharset) {
74+
serial.resetDecoding(selectedCharset);
75+
PreferencesData.set("serial.receive_encoding", selectedEncodingStr);
76+
}
77+
}
78+
});
79+
5980
onSendCommand(new ActionListener() {
6081
public void actionPerformed(ActionEvent e) {
6182
send(textField.getText());
@@ -85,11 +106,48 @@ private void send(String s) {
85106
default:
86107
break;
87108
}
88-
if ("".equals(s) && lineEndings.getSelectedIndex() == 0 && !PreferencesData.has("runtime.line.ending.alert.notified")) {
109+
if ("".equals(s) && lineEndings.getSelectedIndex() == 0
110+
&& !PreferencesData.has("runtime.line.ending.alert.notified")) {
89111
noLineEndingAlert.setForeground(Color.RED);
90112
PreferencesData.set("runtime.line.ending.alert.notified", "true");
91113
}
92-
serial.write(s);
114+
EncodingOption encodingOption =
115+
EncodingOption.forName(PreferencesData.get("serial.send_encoding"));
116+
if (encodingOption == null) {
117+
encodingOption = DEFAULT_SEND_ENCODING;
118+
}
119+
Charset charSet = encodingOption.getCharset();
120+
byte[] bytes;
121+
if (charSet != null) {
122+
bytes = s.getBytes(encodingOption.getCharset());
123+
} else {
124+
switch (encodingOption) {
125+
case BYTES:
126+
String[] split = s.split(",");
127+
bytes = new byte[split.length];
128+
for (int i = 0; i < split.length; i++) {
129+
String valStr = split[i].trim();
130+
try {
131+
int val = Integer.parseInt(valStr);
132+
if (val < 0x00 || val > 0xFF) {
133+
this.message("\n[ERROR] Invalid byte value given: "
134+
+ val + ". Byte values are in range [0-255].\n");
135+
return;
136+
}
137+
bytes[i] = (byte) val;
138+
} catch (NumberFormatException e) {
139+
this.message("\n[ERROR] Invalid byte value given: " + valStr
140+
+ ". Byte values are numbers in range [0-255].\n");
141+
return;
142+
}
143+
}
144+
break;
145+
default:
146+
throw new Error(
147+
"Unsupported 'send as' encoding option: " + encodingOption);
148+
}
149+
}
150+
serial.write(bytes);
93151
}
94152
}
95153

@@ -98,10 +156,27 @@ public void open() throws Exception {
98156

99157
if (serial != null) return;
100158

101-
serial = new Serial(getBoardPort().getAddress(), serialRate) {
159+
EncodingOption encodingOption =
160+
EncodingOption.forName(PreferencesData.get("serial.receive_encoding"));
161+
if (encodingOption == null) {
162+
encodingOption = DEFAULT_RECEIVE_ENCODING;
163+
}
164+
serial = new Serial(
165+
getBoardPort().getAddress(), serialRate, encodingOption.getCharset()) {
102166
@Override
103167
protected void message(char buff[], int n) {
104-
addToUpdateBuffer(buff, n);
168+
if (serial.getCharset() == null) {
169+
if(buff.length != 0) {
170+
StringBuilder strBuilder = new StringBuilder();
171+
for (int i = 0; i < n; i++) {
172+
strBuilder.append(buff[i] & 0xFF).append("\n");
173+
}
174+
addToUpdateBuffer(
175+
strBuilder.toString().toCharArray(), strBuilder.length());
176+
}
177+
} else {
178+
addToUpdateBuffer(buff, n);
179+
}
105180
}
106181
};
107182
}

0 commit comments

Comments
 (0)