Arduino serial commuication with python

I use an Arduino UNO r3 via USB 3 port attached to a PC running WIN 10 and Python. Additionally i use Uart LIDAR sensor on another USB 3 port. The Arduino converts the status of an attached analog switch (status open or closed) to binary code for the Python script. Everything works fine, however after some time (hours or even days) the python script gets no data anymore from the Arduino. The Arduino sketch looks like this:

#include <avr/wdt.h>  // Include watchdog library

#define ButtonPIN 8
#define LED 13

bool oldState;

void setup() {
 Serial.begin(115200);
 pinMode(ButtonPIN, INPUT_PULLUP);
 pinMode(LED, OUTPUT);
 bool state = digitalRead(ButtonPIN);
 sendMessage(state);
 oldState = state;
 Serial.println("Setup complete");
 delay(1000);  // Ensure setup message is sent before entering the loop
 wdt_enable(WDTO_8S);  // Enable watchdog timer with an 8-second timeout
}

void loop() {
wdt_reset();  // Reset the watchdog timer

bool state = digitalRead(ButtonPIN);
 if (oldState != state) {
  digitalWrite(LED, state);
  sendMessage(state);
  delay(500);  // Debounce delay
  oldState = state;
 }
}

void sendMessage(bool state) {
 Serial.write(0x01);
 Serial.write(0x29);
 Serial.write(state ? 0x01 : 0x00);
 Serial.write(0xEE);
 Serial.write('\n');
}

How can i debug this? In the serial monitor of the Arduino IDE things are looking correct, just in python the switcg stos working at some point. I already checked hardware connection, OS sleep modus, bios setting for USB ports, etc. I can also trigger the failure by toggling the hardware switch many, many time realy fast. The adurino seems to stop cumminication. If i press the hardware reset button on the UNO all is back to normal. Is it a buffer issue?

Arduino UNO has a limited serial buffer size. If you're sending data too rapidly from the Arduino or if the Python script is not reading data fast enough, the buffer may overflow, causing communication to stop. You can try adding some delays in your Arduino code to slow down the data transmission, or optimize your Python script to read data more efficiently.

Remove the bool from this statement.

Remove the bool from this statement.

Add "bool state;"
after this statement.

No. Arduino incoming buffer can overflow, yes indeed. Outbound will not. Instead, serial becomes blocking function, not returning from print statements until outbound chars are safely inserted in TX buffer. Nothing gets lost.

Thanks for trying to help, but this code let´s my python script think the switch is closed all the time, no matter how the actual state of the hardware switch is.

So do what you should do in these situations, post the whole of your code again, so we can check you understood what you were told. What you were told about is wrong and needs correcting.

It is also entirely possible that that there is other stuff wrong with your code. I only looked at the incorrect way you were creating those variables.

Remember it is you who are asking the questions here.

@nanniapu your code modified by @Grumpy_Mike is working for me, can we see your Python script

Yes, of course. Here is the python part:

import os
import serial
from threading import Event, Thread
from time import sleep, time

import vlc
import fnmatch

# detect distance
person_detect_distance = 160  # distance in cm

# global variables
videoNo = 1
fadeVideo = False
startStatus = False
personStatus = False
directoryStatus = True
previousButtonStatus = False

buttonEvent = Event()

# Set up Lidar sensor
ser = serial.Serial("COM3", 115200, timeout=1)
button = serial.Serial("COM4", 115200, timeout=1)

# Set up VLC player
instance = vlc.Instance()
player = instance.media_player_new()
player.set_fullscreen(True)

# Define the video directory
primary_directory = r"C:\playlist_de"
secondary_directory = r"C:\playlist_en"

# count .mp4 files in directory. both directories need same amount of files
count = (len(fnmatch.filter(os.listdir(primary_directory), '*.mp4')) - 1) * 2 / 3
print('Videos total:', count)

def is_binary_message(message):
    return len(message) == 4 and message[0] == 0x01 and message[1] == 0x29 and message[3] == 0xEE

def process_data(buffer):
    while b'\n' in buffer:
        newline_index = buffer.index(b'\n')
        line = buffer[:newline_index].strip()
        buffer = buffer[newline_index + 1:]
        
        if is_binary_message(line):
            if line[2] == 0x01:
                return primary_directory
            elif line[2] == 0x00:
                return secondary_directory
            else:
                print("Issue")
        else:
            print(f"Debug message: {line}")
    return None

def set_video_directory():
    global video_directory, buttonEvent
    data_buffer = bytearray()
    last_read_time = time()

    while True:
        try:
            if button.isOpen():
                count_byte = button.in_waiting
                if count_byte > 0:
                    last_read_time = time()
                    byte_data = button.read(count_byte)
                    data_buffer.extend(byte_data)
                    print(f"Raw data received: {byte_data}")

                    new_directory = process_data(data_buffer)
                    if new_directory:
                        video_directory = new_directory
                        buttonEvent.set()
                        print(video_directory)
                    data_buffer.clear()  # Clear buffer after processing

                current_time = time()
                if current_time - last_read_time > 10:
                    print("No data received for 10 seconds.")
                    last_read_time = current_time

            else:
                print("Arduino not responding, attempting to open the port...")
                button.open()
        except serial.SerialException as e:
            print(f"Serial exception: {e}")
        except Exception as e:
            print(f"Unexpected error: {e}")
        
        sleep(0.5)

def fade_out_video():
    fade_duration = 100
    fade_step = 10
    current_volume = player.audio_get_volume()

    while current_volume > 0:
        current_volume -= (fade_step / fade_duration) * 100
        player.audio_set_volume(int(current_volume))
        sleep(fade_step / 1000.0)

def play_blank():
    video_filename = "pause.mp4"
    video_path = os.path.join(video_directory, video_filename)
    print(video_path)
    media = instance.media_new(video_path)
    player.set_media(media)
    player.audio_set_volume(0)
    player.play()
    while player.get_state() != vlc.State.Ended:
        pass

def play_rendered_fade(video_number):
    video_filename = f"{video_number}_fadein.mp4"
    video_path = os.path.join(video_directory, video_filename)
    print(video_path)
    media = instance.media_new(video_path)
    player.set_media(media)
    player.audio_set_volume(0)
    player.play()
    while player.get_state() != vlc.State.Ended:
        pass

def play_videos(video_number):
    video_filename = f"{video_number}.mp4"
    video_path = os.path.join(video_directory, video_filename)
    print(video_path)
    media = instance.media_new(video_path)
    player.set_media(media)
    player.audio_set_volume(100)
    player.play()

    while player.get_state() != vlc.State.Ended:
        if buttonEvent.is_set():
            break
        if startStatus and personStatus:
            distance = read_tfluna_data()
            if distance > person_detect_distance:
                print("Person Left")
                if video_number % 2 == 0:
                    fade_out_video()
                    player.pause()
                    play_blank()
                else:
                    player.pause()
                break

def read_tfluna_data():
    if not ser.isOpen():
        ser.open()

    while True:
        counter = ser.in_waiting
        if counter > 8:
            bytes_serial = ser.read(9)
            ser.reset_input_buffer()

            if bytes_serial[0] == 0x59 and bytes_serial[1] == 0x59:
                distance = bytes_serial[2] + bytes_serial[3] * 256
                return distance

if __name__ == '__main__':
    print("Start")
    video_directory = primary_directory
    button_thread = Thread(target=set_video_directory)
    button_thread.start()
    play_rendered_fade(videoNo)
    play_videos(videoNo)
    while True:
        if buttonEvent.is_set():
            buttonEvent.clear()
            play_videos(videoNo)
        else:
            distance = read_tfluna_data()
            if distance <= person_detect_distance:
                print("Person Detected")
                startStatus = True
                personStatus = True
                fadeVideo = True
                videoNo += 1
                if videoNo > count:
                    videoNo = 1
            else:
                print("No one Present")
                personStatus = False
                if startStatus:
                    if videoNo % 2 == 1:
                        videoNo += 2
                    else:
                        videoNo += 1
                    if videoNo > count:
                        videoNo = 1
                    startStatus = False
                    fadeVideo = True
            if videoNo % 2 == 1:
                if fadeVideo:
                    play_rendered_fade(videoNo)
                    fadeVideo = False
            play_videos(videoNo)

    ser.close()
    button.close()

and the sketch i am using now looks like this:

#include <avr/wdt.h>  // Include watchdog library

#define ButtonPIN 8
#define LED 13

bool oldState;

void setup() {
  Serial.begin(115200);
  pinMode(ButtonPIN, INPUT_PULLUP);
  pinMode(LED, OUTPUT);
  bool state = digitalRead(ButtonPIN);
  sendMessage(state);
  oldState = state;
  Serial.println("Setup complete");
  delay(1000);  // Ensure setup message is sent before entering the loop
  wdt_enable(WDTO_8S);  // Enable watchdog timer with an 8-second timeout
}

void loop() {
  wdt_reset();  // Reset the watchdog timer

  bool state = digitalRead(ButtonPIN);
  if (oldState != state) {
    digitalWrite(LED, state);
    sendMessage(state);
    delay(500);  // Debounce delay
    oldState = state;
  }
}

void sendMessage(bool state) {
  Serial.write(0x01);
  Serial.write(0x29);
  Serial.write(state ? 0x01 : 0x00);
  Serial.write(0xEE);
  Serial.write('\n');
}
//vers3

Which is not what I told you to do!
What is the point of telling you what to do if you just ignore me?

Sorry, well i tried it @Grumpy_Mike and it did not work. Since i need to use the application in a live environment over the weekend i need to revert to a version that at least runs the primary language without error. But when i tried your code it was stuck in the secondary language. The problem is maybe not even code related but a hardware issue which i am also investigating. I will post your proposal here again, i maybe made an error transfering the changes to the sketch:

#include <avr/wdt.h>  // Include watchdog library

#define ButtonPIN 8
#define LED 13

bool oldState;
bool state;

void setup() {
  Serial.begin(115200);
  pinMode(ButtonPIN, INPUT_PULLUP);
  pinMode(LED, OUTPUT);
  digitalRead(ButtonPIN);
  sendMessage(state);
  oldState = state;
  Serial.println("Setup complete");
  delay(1000);  // Ensure setup message is sent before entering the loop
  wdt_enable(WDTO_8S);  // Enable watchdog timer with an 8-second timeout
}

void loop() {
  wdt_reset();  // Reset the watchdog timer
  digitalRead(ButtonPIN);
  if (oldState != state) {
    digitalWrite(LED, state);
    sendMessage(state);
    delay(500);  // Debounce delay
    oldState = state;
  }
}

void sendMessage(bool state) {
  Serial.write(0x01);
  Serial.write(0x29);
  Serial.write(state ? 0x01 : 0x00);
  Serial.write(0xEE);
  Serial.write('\n');
}
//vers3

I can see no attempt to modify anything I told you about. It just looks like the original code to me.

Consider:-

What are you having difficulties with, in the list of changes I suggested?

OK so you changed the code while I was replying.

sorry again, copy and pasted the wrong (old) version. I edited the code above. That should show the changes i made according to your suggestions.

And are you saying that this has another fault?

It did. I don´t exclude human error on my side for sure, it was late when i tried it. Will try again after the weekend when i can access the application. Do you mind telling me why it is better not to store the pin state. Does this cause a buffer overflow?

So you read the value of the buttonPIN but you do not do anything with the result you just throw it away?
Should that line not be:-

state = digitalRead(ButtonPIN)

oh yes for sure. thanks.

Update: just went back into the exbibition space, had a few hours left before the opening. Uploaded the new sketch again and this time no errors, switches were working. So it was my bad after all. I will get back in about 50 hours to report if the UNO keeps working or not. Thanks!

Been back to the machine after 13 hours, switches are still working. Great.
However when i switched off the power in the exhibition space (only the PC stays all the time on power) and then switched it on again, the UNOs were jammed in one language and the switch did not work again. The UNOs are in a metal case but still close to 240V current circuits. Is it actually possible to extend the UNO sketch, so each time the switch is toggled the UNO first resets and than reads the state of the button?

It is possible that the PC which was on all the time provided some parasitic powering of the Arduino. This can cause a situation called latch up, where it doesn't work. Most of the time a compleat powering down of everything will restore correct operation, but occasionally it can damage some devices.

A metal box is not the best thing for firing RF signals through.

Well i guess in that case the PC needs to be switched off before the rest gets shut down. I don’t get your hint about the RF signal…gear is all wired up.

@nanniapu this is a better way of opening the serial, it gives you more control over just writing everything in one go.

Pay attention to "button.dtr = True" when dtr goes from False to True in your Pyton script it resets the Arduino UNO. Below it sends the reset when the port is first opened but you can send the dtr command at any time while your Python script is running when you need a reset.

button = serial.Serial()
button.port = "COM4"
button.baudrate = 15200
button.dtr = True
button.open()