0

I'm encountering an issue where I'm trying to trigger a Python script in the background upon plugging in up to four USB devices, all with the same USB vendor and product IDs.

To accomplish this, I've created a udev rule in /etc/udev/rules.d/myrule.rules as follows:

ACTION=="add", SUBSYSTEM=="usb", ATTR{idVendor}=="1234", ATTR{idProduct}=="5678", ENV{SYSTEMD_WANTS}="usb-trigger.service"

1234 and 5689 are just examples.

Additionally, I've set up a systemd service in /etc/systemd/system/usb-trigger.service with the following configuration:

[Unit]
Description=USB Trigger Service

[Service]
Type=oneshot
ExecStart=/path/to/my/python/script/uart.py

[Install]
WantedBy=multi-user.target

However, the script is sometimes executed and sometimes not. Furthermore, if I plug in more than the first device or the second device, the background script fails to start. Occasionally, none of the devices work as expected.

The primary goal of my Python script is to generate a CSV file upon plugging in a USB device, save the data, and close the file when the device is unplugged. I need this script to handle up to four devices.

Here's the Python script I'm using:

python
import serial
import serial.tools.list_ports
import time
import csv
import datetime
import sys

def get_current_time():
    current_time = time.time()
    milliseconds = int((current_time - int(current_time)) * 1000)
    formatted_time = time.strftime("%H:%M:%S", time.localtime(current_time))
    formatted_time += f".{milliseconds:03d}"  # Adding milliseconds
    return formatted_time

def write_to_csv(writer, data):
    try:
        writer.writerow(data)
    except Exception as e:
        print(f"Error writing to CSV: {e}")

def find_com_port():
    try:
        com_ports = serial.tools.list_ports.comports()
        for port in com_ports:
            if port.device.startswith('/dev/ttyUSB') or port.device.startswith('/dev/ttyACM'):
                return port.device
    except Exception as e:
        print(f"Error finding COM port: {e}")

def main():
    try:
        while True:
            com_port = find_com_port()
            if com_port is not None:
                print(f"COM port found: {com_port}")
                break  # Exit the loop once a COM port is found
            else:
                print("No COM port found. Retrying...")
                time.sleep(1)      

        if com_port is None:
            print("No COM port found.")
            return

        filename = datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S") + ".csv"
        bottle_id = None
        bottle_id_collected = False  # Flag to track if bottle ID has been collected

        with serial.Serial(port=com_port, baudrate=460800, timeout=2) as ser, open(filename, 'w', newline='') as file:
            print(f"Connected to {com_port} successfully.")
            writer = csv.writer(file)

            while True:
                received_bytes = ser.readline().decode('ascii', errors='replace').strip()
                if received_bytes:
                    if not bottle_id_collected and 'Bottle ID' in received_bytes:
                        bottle_id = received_bytes.split('Bottle ID')[1].strip()
                        writer.writerow(['Bottle ID', bottle_id])
                        writer.writerow(['Input 1', 'Input 2', 'Input 3', 'time stamp'])
                        bottle_id_collected = True
                    else:
                        parts = received_bytes.split(', ')
                        try:
                            numbers = [float(part) for part in parts]
                            data_row = numbers + [get_current_time()]
                            writer.writerow(data_row)
                            print(f"Writing to CSV: {data_row}") 
                            file.flush()  # Force flushing
                        except ValueError as e:
                            pass
                time.sleep(0.01)

    except serial.SerialException as e:
        print(f"Error opening serial port {com_port}: {e}")

    except KeyboardInterrupt:
        print("Program terminated by user.")

    except Exception as e:
        print(f"An error occurred: {e}")

    finally:
        try:
            if 'ser' in locals() and ser.is_open:
                ser.close()
        except Exception as e:
            print(f"Error closing serial port: {e}")

        try:
            if 'file' in locals() and not file.closed:
                file.close()


        except Exception as e:
            print(f"Error closing CSV file: {e}")

        sys.exit()

if __name__ == "__main__":
    main()

Any insights into why the script behaves inconsistently and how I can ensure it runs reliably upon plugging in multiple USB devices would be greatly appreciated. Thank you!

I configured a udev rule and systemd service to trigger a Python script upon plugging in multiple USB devices with identical IDs. I expected the script to reliably generate a CSV file for each device, handling up to four devices simultaneously. However, the script sometimes failed to start, especially with multiple devices plugged in, leading to inconsistent behavior.

3
  • Welcome. Have you looked at systemd status usb-trigger and journal -xeu usb-trigger? It might be useful if you edit that information into the question.
    – goldilocks
    Commented Apr 20 at 12:32
  • Please clarify your specific problem or provide additional details to highlight exactly what you need. As it's currently written, it's hard to tell exactly what you're asking.
    – Community Bot
    Commented Apr 22 at 17:30
  • udev probably won't do what you want, a lot of functionality has been removed from it. See unix.stackexchange.com/a/752032 for more details.
    – Peter bill
    Commented Apr 26 at 12:18

1 Answer 1

0

I don't know whether your script will work but as posted it wouldn't even run.

The systemd service needs to have the full path to python.

The python script itself is missing a shebang (and needs to have run permission) unless it is called by python.

NOTE there are dozens of similar questions.

4
  • Hi Milliways, Are you suggesting adding this path to the service unit: ExecStart=/usr/bin/python3 /path/to/my/python/script/uart.py?'Currently, I can only run the script reliably when I plug in just one device. However, I plan to run multiple devices and manage them with this service. Do you recommend creating separate services for each single device when plugged in, or can the service be used repeatedly for multiple devices? Also, if necessary, how can I grant the required run permissions you mentioned? Thank you!
    – Figaro
    Commented Apr 20 at 11:15
  • PS: I'll also add the shebang #!/usr/bin/env python3 line to the top of the script. If you have any further suggestions, thank you.
    – Figaro
    Commented Apr 20 at 11:23
  • As I said "I don't know whether your script will work" because it is not commented and I have no idea what it will do; I was merely pointing out an obvious error. You have not provided any information which would enable anyone to know. For some reason new users seem to be fixated on running things on boot. If I want to do something I manually run a script.
    – Milliways
    Commented Apr 20 at 12:31
  • Apologies for not specifying earlier that the script works fine when I manually push the "Run" button. I would like to seek assistance from old user who might have encountered similar issues in the past.
    – Figaro
    Commented Apr 20 at 12:46

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