2
\$\begingroup\$

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
\$\endgroup\$
  • \$\begingroup\$ What level of timing precision are you aiming for? \$\endgroup\$ – 200_success Mar 24 '16 at 18:23
  • \$\begingroup\$ @200_success Not entirely sure how to answer that, but I'd like to keep up to the seconds precision intact since I'm checking for the 15th and 45th minute-marks for each second passed. Knowing that the script will run for months, I worry that the sub-seconds will eventually spill drifts to the seconds and upwards. \$\endgroup\$ – Nogurenn Mar 24 '16 at 18:35
  • \$\begingroup\$ Welcome to Code Review! Good job on your first question. \$\endgroup\$ – SirPython Mar 24 '16 at 19:43
2
\$\begingroup\$
  • 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.

\$\endgroup\$
  • \$\begingroup\$ Clarification: the 'select.select()' would replace the lines inside the 'wait_until_sampling()' function? As for the sleep, I have to wait for the data to be written to the serial port since the Arduino will take samples over a 5-minute interval after receiving the command, and send the averaged results. \$\endgroup\$ – Nogurenn Mar 25 '16 at 2:30
  • 1
    \$\begingroup\$ @Nogurenn Yes, wait_until_sampling() becomes timeout = compute_timeout(); select.select([], [], [], timeout);. As for sleep, the read() is blocking and your program wouldn't proceed until Arduino replies - with sleep you are only creating a chance of serial overrun. Both select and read end up in system calls, letting your system do other things. \$\endgroup\$ – vnp Mar 25 '16 at 3:03
  • \$\begingroup\$ If I understand this correctly, I don't have to sleep() because the program already just waits for valid input? I intended for the RPi to not be busy while waiting for the ~5-minute sampling time of the Arduino. Can I just call another select.select(), or read as you said will let my system do other things? \$\endgroup\$ – Nogurenn Mar 25 '16 at 4:12
  • \$\begingroup\$ @Nogurenn You got it right. \$\endgroup\$ – vnp Mar 25 '16 at 5:32

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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