I want to send commands to Arduino over USB Serial from my python program and Arduino should send back received command to make sure data wasn't lost. However Arduino randomly stops sending data back. For debugging purposes I made builtin LED blink in the loop function. When Arduino stops sending data the LED slows down blinking (about 2-3 times). I tried this on Arduino Uno and Arduino Nano to make sure it is not damaged board.
Here is my Python code responsible for communication:
class Communication(ABC):
def __init__(self) -> None:
self.history: list = []
self.response_history: list = []
self.last_responses: dict = dict()
self.queue: collections.deque[str] = collections.deque()
self.is_sending: bool = False
self.command_recived_condition: threading.Condition = threading.Condition()
def append_commands(self, commands: str) -> None:
commands = map(lambda x: "c"+x, map(lambda x: x+";", filter(len, commands.split(";"))))
for command in commands:
self.queue.append(command)
def append_and_send(self, commands: str) -> None:
self.append_commands(commands)
if not self.is_sending:
self.sender: threading.Thread = threading.Thread(target=Communication.send_commands, args=(self, ))
self.sender.start()
@abstractmethod
def send_command(self, command: str) -> bool:
""" Sends command to the controller, waits for the response and tries again if command was not recived succesfully """
pass
@abstractmethod
def listen(self) -> None:
pass
@abstractmethod
def check_for_response(self, command: str) -> None:
pass
def send_commands(self):
self.is_sending = True
commands = self.queue
while len(commands) > 0:
command = commands.popleft()
self.send_command(command)
self.is_sending = False
class Serial_comunication(Communication):
def __init__(self, port: str) -> None:
super().__init__()
self.serialcom = serial.Serial(port, 4800)
self.listen()
time.sleep(0.1)
self.send_command("PING;")
time.sleep(0.1)
def listen(self) -> None:
def listen_util() -> None:
chars = []
while True:
if self.serialcom.inWaiting() > 0:
time.sleep(0.01)
try:
char: str = self.serialcom.read().decode()
except UnicodeDecodeError:
print("decoding err")
continue
print(char, end=" ")
if char[-1] == '#':
response_str = "".join(chars)
category, content = response_str.split("::")
self.last_responses[category] = content
self.response_history.append(response_str)
chars.clear()
print("\n", response_str)
if category == 'Info: recived':
with self.command_recived_condition:
self.command_recived_condition.notify()
else:
chars.append(char)
time.sleep(0.01)
self.listener = threading.Thread(target=listen_util)
self.listener.start()
def check_for_response(self, command: str) -> None:
print("check")
if self.last_responses.get('Info: recived') == command:
print('possitive')
return True
return False
def send_command(self, command: str) -> None:
while True:
time.sleep(0.1)
self.serialcom.write(command.encode())
print("sent", command)
with self.command_recived_condition:
self.command_recived_condition.wait()
print("waited")
if self.check_for_response(command.strip().strip(';')):
break
And my Arduino Code responsible for communication:
#define INPUT_SIZE 50
char input[INPUT_SIZE + 1];
bool check_for_orders(Order& cmd, char *val, uint8_t& place)
{
if (Serial.available() > 0)
{
// Read input from Serial until ; what means command has ended
size_t size = Serial.readBytesUntil(';', input, INPUT_SIZE);
input[size] = 0; // Add null terminator
Serial.print(F("Info: recived::"));
delay(10);
Serial.print(input);
Serial.print(F("#"));
Serial.flush();
// Check if command is not empty
if (input[0] != 0)
{
// syntax is "order:pattern_place:value"
cmd = static_cast<Order>(atoi(strtok(((char*)input)+1, ":"))); // Get order numer and convert to enum
place = atoi(strtok(NULL, ":")); // Get place number from input (only 0 or 1 is allowed)
char *value = strtok(NULL, ":"); // Get value from second part of string
if (value == 0) // If value is empty then syntax is invalid
{
return false;
}
// Otherwise copy order name and value to c-strings from main function
strcpy(val, value);
free(value);
return true;
}
else // Probably never gonna happen
{
return false;
}
}
return false;
}
The check_for_orders function is run every time loop function is run.
Here is what python printed when I sent the following commands ["PING;", "c0:0:0;", "c0:1:0; c15:0:100,600,20,2,0,200,255,170,255,200;"]
sent PING;
I n f o : r e c i v e d : : P I N G #
Info: recived::PING
waited
check
possitive
sent c0:0:0;
I n f o : r e c i v e d : : c 0 : 0 : 0 #
Info: recived::c0:0:0
waited
check
possitive
sent c0:1:0;
I n f o : r e c i v e d : : c 0 : 1 : 0 #
Info: recived::c0:1:0
waited
check
possitive
sent c15:0:100,600,20,2,0,200,255,170,255,200;
I n f o : r e c i v e d : : c 1 5 : 0 : 1 0 0 , 6 0 0 , 2 0 , 2 , 0 , 2 0 0 , 2 5 5 , 1 7 0 , 2 5 5 , 2 0 0 #
Info: recived::c15:0:100,600,20,2,0,200,255,170,255,200
waited
check
possitive
sent c1:0:1;
/* and it got stuck */
Do someone know why something here is getting stuck and/or how to solve this problem?
1 Answer 1
I removed free(value)
- rookie mistake - but that didn't completely solved the problem (but reduced frequency of occurrences).
Seems like declaring array like char val[50]
and passing it to function as char* val
was a bad idea. I changed array declaration to char *val = new char[50]
and now seems like everything works fine.
-
1Sending
char* val
is fine as long as: 1. you don't overflow theval
; 2. you allow the last byte for NULL byte; 3. theval
is always NULL terminated, no matter the size or you also send the size of itBinar Web– Binar Web2021年07月10日 06:53:02 +00:00Commented Jul 10, 2021 at 6:53
free(value)