RPi script to periodically collect data from Arduino through serial port I'm not used to Python.

The script opens serial communications with an Arduino unit and send commands to and receives information from the Arduino. It is intended to run indefinitely until the system is shut down or pulled out of field deployment/laboratory testing.

The time.sleep() calls with offsets are assurances that repeated calls won't eventually result to time drifts, as mentioned in this Stack Overflow Q/A. I don't trust myself with this. I need confirmation that I placed the start_time = time.time() lines in proper locations. This time drift issue is crucial because the script shall run for months.

read_data() sends a command to the Arduino, which tells it to give the Pi new data. Data sets written by the Arduino vary in length, so I can't use serial.read(expected_bytecount). Data sent between start and stop bytes (e.g. *data1,data2,data3#) is always valid, so I don't need to do checks there. Is my algorithm here enough for my task?

wait_until_sampling() keeps the Pi from being busy around the clock. Reading data occurs every 15th and 45th minutes of an hour. I thought of using minutes % 15 != 0, but there are the 0th and 30th marks. I also tried doing away with datetime but I can't get the minutes of the system time from time. Is there a simple way to improve this algorithm by dropping datetime?

import datetime
import time
import serial


def read_data():
    data = []
    start_time = time.time()
    arduino.write(b'S')
    time.sleep(360.0 - ((time.time() - start_time) % 360.0))
    while True:
        temp = arduino.read().decode(encoding='UTF-8')
        if temp  == "*":
            while True:
                temp = arduino.read().decode(encoding='UTF-8')
                if temp == "#":
                    break
                else:
                    data.append(temp)
            break
    return data


def wait_until_sampling():
    start_time = time.time()
    dt_now = datetime.datetime.now().time()
    while (dt_now.minute != 45) and (dt_now.minute != 15):
            time_now = time.time()
            dt_now = datetime.datetime.now().time()
            time.sleep(1.0 - ((time_now - start_time) % 1.0))



arduino = serial.Serial('/dev/ttyACM0', 9600)
time.sleep(20.0)
data_returned = []
while True:
        wait_until_sampling()
        data_returned = read_data()
        # post-process data_returned here
A simple technique to avoid busy wait is to

import select
....
select.select([], [], [], timeout)


The timeout is simply target_time - current_time in seconds. Since select interprets it as a floating point, the granularity is in  milliseconds at least.

I see no possibility for time drift.
The nested loops in read_data do not look right. Consider instead

def read_data():
    data = []

    temp = None
    while temp != '*':
        temp = arduino.read()...

    while temp != '#':
        temp = arduino.read()...
        data.append(temp)

    return data[:-1]

I am not sure why do you sleep() before reading the response.