For an university assignment I'm writing a java application that will run some game logic for an interactive LED table. The table itself is being controlled by either 2 Arduino Duemilanove or 1 Arduino Mega 2560.
To give the Arduino(s) information about which LEDs should be lit in which color I send the data over the serial port from a Raspberry Pi 3b+ to the Arduinos. As the table consists of 14 LED strips with 14 LEDs per LED strip and each LED has 3 color values (RGB) I store the data about the table in an int[14][14][3] array.
Before sending the array to the Arduino I create a JSON object of it (using the Jackson library) and then send the array as a String using jSerialComm. Depending on which Arduino setup I use I also either transfer the whole array to JSON or split it into two int[7][14][3] arrays before creating the JSON object.
As the data arrived in the wrong order at the serial port when I used 2 Arduinos and jSerialComm I now got a new Arduino Mega 2560 (as other SO questions suggested the wrong data order might occur due to an outdated PL2303 module) and tried it again with the same result. After some further research I now tried using JSSC instead of jSerialComm but still the same result shows up.
The java class that I use to send the data to the arduino looks like this (the outcommented code being the code where I used jSerialComm / 2 Arduinos):
package de.pimatrix.backend;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.net.ServerSocket;
import java.net.Socket;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fazecast.jSerialComm.SerialPort;
import jssc.SerialPortException;
public class SerialThread implements Runnable {
public static SerialPort arduino1, arduino2;
private int[][][] matrix = new int[14][14][3];
private int[][][] matrixLeft = new int[7][14][3];
private int[][][] matrixRight = new int[7][14][3];
private Socket localHost;
private Matrix matrixData;
private ObjectInputStream in;
#Override
public void run() {
SerialJSONWriter writer = new SerialJSONWriter();
ServerSocket ss = null;
localHost = null;
matrixData = new Matrix(matrix);
try {
ss = new ServerSocket(62000); // erstellen eines lokalen Sockets auf Port 62000, um die zu übertragende
// Matrix vom ClientThread
} catch (IOException e) {
}
while (true) {
try {
localHost = ss.accept();
} catch (Exception e) {
e.printStackTrace();
}
initializeInputStream();
waitForMatrix();
splitMatrix();
try {
writer.tryWrite(matrixRight, matrixLeft);
} catch (Exception e) {
e.printStackTrace();
}
}
}
private void splitMatrix() {
for (int i = 0; i < 14; i++) {
for (int j = 0; j < 14; j++) {
if (i <= 6) {
matrixRight[i][j][0] = matrix[i][j][0];
matrixRight[i][j][1] = matrix[i][j][1];
matrixRight[i][j][2] = matrix[i][j][2];
} else {
matrixLeft[i - 7][j][0] = matrix[i][j][0];
matrixLeft[i - 7][j][1] = matrix[i][j][1];
matrixLeft[i - 7][j][2] = matrix[i][j][2];
}
}
}
}
private void initializeInputStream() {
try {
InputStream input = localHost.getInputStream();
in = new ObjectInputStream(input);
} catch (IOException e) {
e.printStackTrace();
}
}
public void waitForMatrix() {
System.out.println("Waiting for Matrix");
try {
matrixData = (Matrix) in.readObject();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
this.matrix = matrixData.matrix;
}
class SerialJSONWriter implements AutoCloseable {
// Zuweisen der seriellen Ports
// private final SerialPort /*arduino1, arduino2,*/ arduinoMega;
private jssc.SerialPort arduinoMega;
public SerialJSONWriter() {
// arduino1 = SerialPort.getCommPort("COM5");
// arduino2 = SerialPort.getCommPort("COM6");
// arduinoMega = SerialPort.getCommPort("COM7");
arduinoMega = new jssc.SerialPort("COM7");
try {
arduinoMega.openPort();
arduinoMega.setParams(115200, 8, 1, jssc.SerialPort.PARITY_EVEN);
} catch (SerialPortException e) {
e.printStackTrace();
}
// arduinoMega.setBaudRate(115200);
// arduinoMega.setNumDataBits(8);
// arduinoMega.setNumStopBits(1);
// arduinoMega.setParity(0);
// setzen der Timeouts für die Kommunikation mit den Arduinos
// arduino1.setComPortTimeouts(SerialPort.TIMEOUT_SCANNER, 0, 0);
// arduino2.setComPortTimeouts(SerialPort.TIMEOUT_SCANNER, 0, 0);
// arduinoMega.setComPortTimeouts(SerialPort.TIMEOUT_SCANNER, 0, 0);
// arduino1.setBaudRate(115200);
// arduino2.setBaudRate(115200);
// arduinoMega.setBaudRate(115200);
// arduino1.openPort();
// arduino2.openPort();
// arduinoMega.openPort();
// arduino1.setComPortTimeouts(SerialPort.TIMEOUT_READ_SEMI_BLOCKING | SerialPort.TIMEOUT_WRITE_BLOCKING, 0,
// 0);
// arduino2.setComPortTimeouts(SerialPort.TIMEOUT_READ_SEMI_BLOCKING | SerialPort.TIMEOUT_WRITE_BLOCKING, 0,
// 0);
// arduinoMega.setComPortTimeouts(SerialPort.TIMEOUT_READ_SEMI_BLOCKING | SerialPort.TIMEOUT_WRITE_BLOCKING, 0,
// 0);
}
public void write() {
}
private void tryWrite(Object dataRight, Object dataLeft) throws IOException {
String dataAsJSONRight = new ObjectMapper().writeValueAsString(dataRight) + "\n";
String dataAsJSONLeft = new ObjectMapper().writeValueAsString(dataLeft) + "\n";
try {
arduinoMega.writeString(dataAsJSONRight);
} catch (SerialPortException e) {
e.printStackTrace();
}
// for (int i = 0; i < dataAsJSONRight.length(); i++) {
//// arduino1.getOutputStream().write(dataAsJSONRight.getBytes()[i]);
// System.out.println(dataAsJSONRight);
// arduinoMega.getOutputStream().write(dataAsJSONRight.getBytes()[i]);
// }
// for (int i = 0; i < dataAsJSONLeft.length(); i++) {
//// arduino2.getOutputStream().write(dataAsJSONLeft.getBytes()[i]);
// arduinoMega.getOutputStream().write(dataAsJSONLeft.getBytes()[i]);
// }
}
#Override
public void close() throws Exception {
// arduino1.closePort();
// arduino2.closePort();
arduinoMega.closePort();
}
}
}
On the Arduino(s) the processing looks like this:
#include <ArduinoJson.h>
#include <Adafruit_NeoPixel.h>
#define PINROW0 2
#define PINROW1 3
#define PINROW2 4
#define PINROW3 5
#define PINROW4 6
#define PINROW5 7
#define PINROW6 8
#define NUMPIXELS 14 //Amount of pixels per row
Adafruit_NeoPixel row[] = { //Intitialize the array, that contains the addressable LED strips in the Adafruit format
Adafruit_NeoPixel(NUMPIXELS, PINROW0, NEO_GRB + NEO_KHZ800),
Adafruit_NeoPixel(NUMPIXELS, PINROW1, NEO_GRB + NEO_KHZ800),
Adafruit_NeoPixel(NUMPIXELS, PINROW2, NEO_GRB + NEO_KHZ800),
Adafruit_NeoPixel(NUMPIXELS, PINROW3, NEO_GRB + NEO_KHZ800),
Adafruit_NeoPixel(NUMPIXELS, PINROW4, NEO_GRB + NEO_KHZ800),
Adafruit_NeoPixel(NUMPIXELS, PINROW5, NEO_GRB + NEO_KHZ800),
Adafruit_NeoPixel(NUMPIXELS, PINROW6, NEO_GRB + NEO_KHZ800)
};
#define DELAY 1000 //set refresh cycle to 10 milliseconds
#define NUMSTRIPS 7/*(sizeof(row)/sizeof(row[0]))*/ //Amount of connected LED strips
int values[7][14][3];
int c = 0;
String matrixAsString = "";
void setup() {
/*Setup serial port on which the Pi connects to the Arduino*/
Serial.begin(115200); //set baudrate to 115200 Bit per second
Serial.setTimeout(1000);
Serial.println(100);
/*initialize NeoPixel Library*/
for (int i = 0; i < NUMSTRIPS; i++) {
row[i].begin();
row[i].show();
}
}
void process(String matrixAsString) {
StaticJsonDocument<4372> doc;
Serial.println(matrixAsString);
deserializeJson(doc, matrixAsString);
for (int i = 0; i < 7; i++) {
for (int j = 0; i < 14; j++) {
values[i][j][0] = values[i][j][1] = values[i][j][2] = (int) (doc[i][j][0]);
}
}
}
//infinite loop refreshing the matrix
void loop() {
while (Serial.available()) {
char c = Serial.read();
Serial.println(matrixAsString);
matrixAsString += c;
if (c == '\n') {
process(matrixAsString);
matrixAsString = "";
}
}
}
When sending the data for a half matrix (so an int[7][14][3]):
[[[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0]],[[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0]],[[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[255,0,0],[0,0,0],[0,0,0],[0,0,0]],[[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0]],[[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0]],[[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0]],[[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0]]]
through the serial monitor in the Arduino IDE I get this output from the Arduino (as of the Serial.println() in void loop):
As one can see the first RGB values are transmitted correctly, however after even less than one complete LED strip the data arrives in the wrong order and (as you can see at the end of the picture) at some point completely stops showing up, which probably indicates no data is being read any more.
I've been trying a hell of things like changing the Arduino in case the PL2303 is outdated or defective as well as trying different libraries for serial communication however I cannot figure out what I'm doing wrong. I've spent over 30 hours trying different approaches to no avail so things are becoming really frustrating for me.
UPDATE
As suggested by B.Letz I got the setup of the data, stop and parity bits right (now being 8 data, 1 stop and no parity bits). By reading the arduino feedback I still got the same results but after some tweeking I recognized that the problem probably was that my Serial.print apparently led to a massive lag on the Arduino so it couldn't handle all the data correctly and in time. After removing the first Serial.print call before doing the processing I now see the first matrix that is being transmitted being printed correctly by the Arduino. However for some reason for all further transmitted data the Arduino prints null. I'll try extending the timeouts in case the null pointer occurs due to a timeout on the Arduino-side.
UPDATE 2
Opposed to my assumption reconfiguring the timeouts didn't solve the problem. I also figured out that after the first JSON Object is sent the Arduino prints null to the console and only sends me the first JSON object after receiving the second JSON object. However this is the only time I get any feedback from the Arduino except null. What I also noticed is that when I send the JSON String over the serial monitor the Arduino instantly prints the correct String BUT it also prints an empty new line and doesn't respond to any new data in any kind.
The first step for a working solution was removing the unnecessary Serial.print() call everytime a new char was read. After removing this line I could confirm the data arrived properly.
The shifted feedback as mentioned in my second update to the post:
I also figured out that after the first JSON Object is sent the Arduino prints null to the console and only sends me the first JSON object after receiving the second JSON object. However this is the only time I get any feedback from the Arduino except null
occured due to the fact that I didn't wait long enough on the java application side for data to arrive before calling the read() function. After solving this I always received the correct string.
Trying out different configurations with either DynamicJsonDocument and StaticJsonDocument I now ended up using DynamicJsonDocument however also a StaticJsonDocument might have worked here.
A rather unpleasant issue was that in the inner for-loop in void process I accidently compared the counter variable to that of the outer for-loop, although I was able to retrieve the correct data at that point outside of the for-loop.
The problem asked in this questions is thereby solved, however a even bigger problem now occured as I'm not able to retrieve any data from the received JSON object as soon as I start implementing the code for controlling the LEDs and call row[i].setPixelColor(j, row[i].Color(values[i][j][0], values[i][j][1], values[i][j][2])); at any point in my code. So to sum things up this specific call is the actual reason that the code doesn't work properly.
I'll be opening a new question for that new problem as it doesn't belong to this question thematically, however I will add a reference to it here, once it's written.
UPDATE
The new question regarding addressing more than 7 LED strips using Adafruit's NeoPixel library can be found here.
I am using a terminal emulator library to create a terminal and then I use it to send the data entered over serial to a serial device. The library can be seen here.
When I enter data into the terminal a strange series of characters is being sent/received. I think the unicode replacement character gets sent over serial, the serial device doesnt know what it is and returns ~0.
Screenshot of what appears in the terminal when i write "test":
And the log showing the strings sent and the data received.
I create an EmulatorView, it's the terminal view. it mentions the diamonds here.
private void sendText(CharSequence text) {
int n = text.length();
char c;
try {
for(int i = 0; i < n; i++) {
c = text.charAt(i);
if (Character.isHighSurrogate(c)) {
int codePoint;
if (++i < n) {
codePoint = Character.toCodePoint(c, text.charAt(i));
} else {
// Unicode Replacement Glyph, aka white question mark in black diamond.
codePoint = '\ufffd';
}
mapAndSend(codePoint);
} else {
mapAndSend(c);
}
}
} catch (IOException e) {
Log.e(TAG, "error writing ", e);
}
}
Is there any way to fix this? Can anybody see in the library class why this is happening?, How can I refer to � in java to even parse it out if I wanted to? I can't say if (!str.contains("�") I take it.
When I type in the terminal this is run:
public void write(byte[] bytes, int offset, int count) {
String str;
try {
str = new String(bytes, "UTF-8");
Log.d(TAG, "data received in write: " +str );
GraphicsTerminalActivity.sendOverSerial(str.getBytes("UTF-8"));
} catch (UnsupportedEncodingException e) {
Log.d(TAG, "exception" );
e.printStackTrace();
}
// appendToEmulator(bytes, 0, bytes.length);
return;
}
This is what I call to send data. sendData(Byte[] data) is a library method.
public static void sendOverSerial(byte[] data) {
String str;
try {
str = new String(data,"UTF-8");
if(mSelectedAdapter !=null && data !=null){
Log.d(TAG, "send over serial string==== " + str);
mSelectedAdapter.sendData(str.getBytes("UTF-8"));
}
} catch (UnsupportedEncodingException e) {
Log.d(TAG, "exception");
e.printStackTrace();
}
}
Once data is sent the reply is received here:
public void onDataReceived(int id, byte[] data) {
try {
dataReceived = new String(data, "UTF-8");
} catch (UnsupportedEncodingException e) {
Log.d(TAG, "exception");
e.printStackTrace();
}
try {
dataReceivedByte = dataReceived.getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
Log.d(TAG, "exception");
e.printStackTrace();
}
statusBool = true;
Log.d(TAG, "in data received " + dataReceived);
((MyBAIsWrapper) bis).renew(data);
runOnUiThread(new Runnable(){
#Override
public void run() {
mSession.appendToEmulator(dataReceivedByte, 0, dataReceivedByte.length);
}});
viewHandler.post(updateView);
}
Relevant section of library class where characters are written:
Relevant section of class:
private void sendText(CharSequence text) {
int n = text.length();
char c;
try {
for(int i = 0; i < n; i++) {
c = text.charAt(i);
if (Character.isHighSurrogate(c)) {
int codePoint;
if (++i < n) {
codePoint = Character.toCodePoint(c, text.charAt(i));
} else {
// Unicode Replacement Glyph, aka white question mark in black diamond.
codePoint = '\ufffd';
}
mapAndSend(codePoint);
} else {
mapAndSend(c);
}
}
} catch (IOException e) {
Log.e(TAG, "error writing ", e);
}
}
private void mapAndSend(int c) throws IOException {
int result = mKeyListener.mapControlChar(c);
if (result < TermKeyListener.KEYCODE_OFFSET) {
mTermSession.write(result);
} else {
mKeyListener.handleKeyCode(result - TermKeyListener.KEYCODE_OFFSET, getKeypadApplicationMode());
}
clearSpecialKeyStatus();
}
Java stores text internally as unencoded Unicode. Used to be 16 bits, now I'm guessing it's 32 based on the fact that you're getting four characters of output on your terminal for every unicode character you're trying to output.
What you probably want to do is use something like string.getBytes("ASCII") to convert your unicode string into straight single-byte ascii. If your terminal emulator handles other character sets (like Latin-1), use that instead of "ASCII".
Then, transmit the bytes to your terminal emulator instead of the string.
Notes: I'm not positive that "ASCII" is the exact name of the character set; you'll want to research that yourself. Also, I don't know what getBytes() will do with unicode characters that can't be translated to ascii, so you'll want to research that too.
ETA: I'm having trouble following your code logic from the scraps you posted. Who calls write(), where did the data come from, and where does it go? Same questions applies to sendOverSerial() and onDataReceived().
In any event, I'm almost dead certain that somewhere, raw 32-bit Unicode data was converted to bytes without being encoded. From that point forward, either sending it as-is or re-encoding it as UTF-8 would produce the effect you're seeing. I don't see how this could have happened in any of the code you posted, so I'm guessing it happened elsewhere before any of the functions you showed us are being called.
I have solved this issue by editing the library I am using. They were using a method which converted a byte to an int, it accepted a codePoint and converted it. So for every key press 4 bytes are used. I changed this so that a byte is used instead of an int. No more extra bytes. Nothing got to do with the encoding format.
It appears that the library you are using is sending code points as int's (which are 32bit) and your code is assuming its encoded as utf-8 which doesn't handle the 4-bytes properly. This is not related to how java stores text internally. Btw Java stores text internally as encoded UTF-16, not unencoded unicode. Again, this isn't the cause of this issue. It is how you are interacting with the library you are using.