2

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?

2
  • 4800 baud (I guess that is your baudrate) is pretty show. Depending on how often and how much you send you can easily overload the serial interface. Have you tried using a bigger baudrate? And can you provide a minimal working example, that shows your code? I mean a full working code, but without any non relevant code in it and that still shows your problem.
    – chrisl
    Jul 7, 2021 at 11:05
  • 4
    don't free(value)
    – Juraj
    Jul 7, 2021 at 11:52

1 Answer 1

2

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.

1
  • 1
    Sending char* val is fine as long as: 1. you don't overflow the val; 2. you allow the last byte for NULL byte; 3. the val is always NULL terminated, no matter the size or you also send the size of it
    – Binar Web
    Jul 10, 2021 at 6:53

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.