I have a simple text parser (as a component of a larger project) coded up and seemingly working correctly. It parses a key-value delimited command like the following: <key1=value1;key2=value2;key3=value3>.
At present, the functionality is very silly just to test it out: when the Arduino finds a key named "text", it composes a response of a similar form, but just with one key "text_return" and the same value, and sends it back via serial. That is, when the command <key1=value1;text=123;key3=value3> is sent, I expect a response from the Arduino of <text_return=123>.
I then am connecting the board to my laptop via USB, and testing it out, both with the Serial Monitor in Arduino's IDE, and with a simple program coded in python.
So here's the confusing part:
- Serial Monitor with Uno: works
- Python with Uno: works
- Serial Monitor with Mega: works
- Python with Mega: does NOT work
Specifically, by "does not work", I mean that the Mega seems to receive the serial input from the program, but does not issue a response.
Here's the terminal output from test #2 as displayed by the python program, showing the command-response I'd expect (I parsed the received command into a dict):
SENT: <text=1953>
SENT: <text=1954>
SENT: <text=1954>
SENT: <text=1954>
SENT: <text=1954>
SENT: <text=1954>
SENT: <text=1954>
SENT: <text=1954>
RECD: {'text_return': 1954}
SENT: <text=1954>
RECD: {'text_return': 1954}
SENT: <text=1954>
RECD: {'text_return': 1954}
SENT: <text=1954>
RECD: {'text_return': 1954}
And here's the terminal output from the non-working test#4 - i.e.: no response.
SENT: <text=2001>
SENT: <text=2002>
SENT: <text=2002>
SENT: <text=2002>
SENT: <text=2002>
SENT: <text=2002>
SENT: <text=2002>
SENT: <text=2002>
SENT: <text=2002>
SENT: <text=2002>
SENT: <text=2002>
SENT: <text=2003>
SENT: <text=2003>
SENT: <text=2003>
SENT: <text=2003>
SENT: <text=2003>
SENT: <text=2003>
SENT: <text=2003>
SENT: <text=2003>
In all cases, I'm using 9600 Baud; the only change between the two tests is that I select the Uno vs. Mega / Mega 2560 board in the Arduino IDE.
The Mega board is an Elegoo Mega2560 R3. The code may be extraneous, but to help:
Opening the port to the arduino from python
def FindArduino(baud=9600, timeout=0):
initial_time = time.time()
arduino_found = False
attempted = False
while not attempted or time.time() - initial_time < timeout and not arduino_found:
attempted = True
ports = serial.tools.list_ports.comports(include_links=False)
for port in ports:
manufacturer = port.manufacturer
if manufacturer and 'arduino' in manufacturer.lower():
arduino_port = port
arduino_found = True
if arduino_found:
try:
arduino = serial.Serial(arduino_port.device, baud, timeout=0)
arduino.reset_input_buffer()
arduino.reset_output_buffer()
except serial.SerialException:
arduino_found = False
if arduino_found:
return arduino
else:
return None
Sending the serial in python, to Arduino
cmd = '<text=%d>' % (time.time() - 1586218411)
print('SENT: %s' %cmd)
cmd = bytes(cmd, 'ascii')
arduino.write(cmd)
time.sleep(.1)
Receiving the serial in python, from Arduino
cmds = []
if (arduino.in_waiting>0):
buffer += arduino.read(arduino.in_waiting).decode('ascii')
while COMMAND_END_CHAR in buffer:
end_char_pos = buffer.find(COMMAND_END_CHAR)
potential_command = buffer[:end_char_pos]
if COMMAND_START_CHAR in potential_command:
cmds.append(potential_command[potential_command.find(COMMAND_START_CHAR)+1:])
buffer = buffer[end_char_pos+1:]
return (buffer, cmds)
And the full code on the Arduino itself
// command indicators
char START_MARKER = '<';
char END_MARKER = '>';
const int MAX_KEY_VALUE_PAIRS = 3; // maximum number of key-value pairs in message
const int MAX_ELEMENT_CHARS = 30; // the maximum number of characters (+1 for terminator) in a key or a value
// message format: <key1=value1;key2=value2;key3=value3>
const int MAX_MESSAGE_CHARS = (MAX_KEY_VALUE_PAIRS * (MAX_ELEMENT_CHARS + 1)) * 2 + (MAX_KEY_VALUE_PAIRS - 1) + 2; // maximum message size
char received_chars[MAX_MESSAGE_CHARS];
bool new_data = false;
char written_chars[MAX_MESSAGE_CHARS];
char *text = {'\0'};
void ParseData(char *str) {
// This picks off the ;-delimited key-value pairs and assigns them to a multi-dim array
char * pch;
int pairs_count = 0;
char config[MAX_KEY_VALUE_PAIRS][2][MAX_ELEMENT_CHARS];
pch = strtok(str, "=");
while (pch != NULL)
{
strcpy(config[pairs_count][0], pch);
pch = strtok(NULL, ";");
if (pch == NULL) break;
strcpy(config[pairs_count][1], pch);
pairs_count++;
pch = strtok(NULL, "=");
if (pch == NULL) break;
}
for(int i=0;i<pairs_count;i++) {
if (strcmp(config[i][0], "text")==0)
strcpy(text, config[i][1]);
WriteSerial();
}
}
void ReadSerial(){
// After calling ReceiveText to empty the buffer, if a complete command has been found,
// parse that command.
new_data = ReceiveText();
if (new_data == true) {
char temp_chars[MAX_MESSAGE_CHARS]; // temporary array for use when parsing
strcpy(temp_chars, received_chars);
received_chars[0] = '\0';
ParseData(temp_chars);
}
}
void WriteSerial(){
sprintf(written_chars, "<text_return=%s>\n", text);
Serial.write(written_chars);
written_chars[0] = '\0';
}
boolean ReceiveText() {
// This dumps the characters on the buffer so far received_chars, searching for an END_MARKER
// along the way; if it finds one, it goes back to find a START_MARKER; if that is also found,
// the string within is the ;-delimited set of key value pairs
static boolean recv_in_progress = false;
static byte ndx = 0;
char rc;
boolean new_data = false;
while (Serial.available() > 0 && new_data == false) {
rc = Serial.read();
if (recv_in_progress == true) {
if (rc != END_MARKER) {
received_chars[ndx] = rc;
ndx++;
if (ndx >= MAX_MESSAGE_CHARS) {
ndx = MAX_MESSAGE_CHARS - 1;
}
} else {
received_chars[ndx] = '\0'; // terminate the string
recv_in_progress = false;
ndx = 0;
new_data = true;
}
} else if (rc == START_MARKER) {
recv_in_progress = true;
}
}
return new_data;
}
void setup() {
Serial.begin(9600);
text = (char*)malloc(25);
received_chars[0] = '\0';
}
void loop() {
ReadSerial();
}
UPDATE
Using the suggested libraries, I updated the code. The comms in both directions for simple test data are working, though I can't seem to convert the packet received by the Arduino into a string correctly. Here is the output I see from my python terminal; I'd expect to receive back the first character of what was sent (i.e.: the "0", then "1"), rather than just the initialized value of "S".
SENT: 0.20
RCVD: S
SENT: 0.41
RCVD: S
SENT: 0.61
RCVD: S
SENT: 0.82
RCVD: S
SENT: 1.02
RCVD: S
SENT: 1.23
RCVD: S
SENT: 1.43
RCVD: S
SENT: 1.64
RCVD: S
Python code import time from pySerialTransfer import pySerialTransfer as txfer
if name == 'main': try: link = txfer.SerialTransfer('/dev/cu.usbmodem14201')
link.open()
time.sleep(2) # allow some time for the Arduino to completely reset
base = time.time()
while True:
time.sleep(0.2)
s = '%.2f' % (time.time() - base)
l = len(s)
for i in range(l):
link.txBuff[i] = s[i]
link.send(l)
while not link.available():
if link.status < 0:
print('ERROR: {}'.format(link.status))
response = ''
for index in range(link.bytesRead):
response += chr(link.rxBuff[index])
print('SENT: %s' % s)
print('RCVD: %s' % response)
except KeyboardInterrupt: link.close()
**Arduino code**
#include "SerialTransfer.h"
char str[1000];
SerialTransfer myTransfer;
void setup() { str[0] = 'S'; str[1] = '\n'; Serial.begin(115200); myTransfer.begin(Serial); }
void loop() {
// send bytes myTransfer.txBuff[0] = str[0]; myTransfer.sendData(1); delay(100);
if(myTransfer.available()) { // receive bytes byte bytes_to_read = myTransfer.bytesRead; for(byte i = 0; i < bytes_to_read; i++) str[i] = myTransfer.rxBuff[i]; str[bytes_to_read] = '\0';
} else if(myTransfer.status < 0) { Serial.print("ERROR: ");
if(myTransfer.status == -1)
Serial.println(F("CRC_ERROR"));
else if(myTransfer.status == -2)
Serial.println(F("PAYLOAD_ERROR"));
else if(myTransfer.status == -3)
Serial.println(F("STOP_BYTE_ERROR"));
} }
Thank you!