I am a Python beginner and wrote a script which should run on a Raspberry PI as a UDP server. It reads and writes to a serial device and has to process and forward JSON strings.
Because there is relatively much functionality included and I don't like cross compiling, I was too lazy to write a C program but instead chose Python. To avoid serial read/write blocks I even used threads. However I noticed that sometimes I get timeouts.
Are there some methods to make this script maybe faster or even to guarantee timings? Additionally I am open for style tips.
# Make this Python 2.7 script compatible to Python 3 standard from __future__ import print_function # For remote control import socket import json import serial # For sensor readout import logging import threading # For system specific functions import sys import os import time # Create a sensor log with date and time layout = '%(asctime)s - %(levelname)s - %(message)s' logging.basicConfig(filename='/tmp/RPiQuadrocopter.log', level=logging.INFO, format=layout) # Socket for WiFi data transport udp_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) udp_sock.bind(('0.0.0.0', 7000)) client_adr = "" # Thread lock for multi threading THR_LOCK = threading.Lock() #pySerial pySerial = 0 def init_serial(device_count = 10): counter = 0 baud_rate = '115200' while counter < device_count: com_port = '/dev/ttyACM%d' % (counter) try: # rtscts=1 is necessary for raspbian due to a bug in the usb/serial driver pySerial = serial.Serial(com_port, baud_rate, timeout=0.1, writeTimeout=0.1, rtscts=1) except serial.SerialException as e: logging.debug("Could not open serial port: {}".format(com_port, e)) print ("Could not open serial port: {}".format(com_port, e)) com_port = '/dev/ttyUSB%d' % (counter) try: # rtscts=1 is necessary for raspbian due to a bug in the usb/serial driver pySerial = serial.Serial(com_port, baud_rate, timeout=0.1, writeTimeout=0.1, rtscts=1) except serial.SerialException as e: logging.debug("Could not open serial port: {}".format(com_port, e)) print ("Could not open serial port: {}".format(com_port, e)) if counter == device_count-1: return False, 0 counter += 1 else: return True, pySerial else: return True, pySerial #chksum calculation def chksum(line): c = 0 for a in line: c = ((c + ord(a)) << 1) % 256 return c def send_data(type, line): # calc checksum chk = chksum(line) # concatenate msg and chksum output = "%s%s*%x\r\n" % (type, line, chk) try: bytes = pySerial.write(output) except serial.SerialTimeoutException as e: logging.error("Write timeout on serial port '{}': {}".format(com_port, e)) finally: # Flush input buffer, if there is still some unprocessed data left # Otherwise the APM 2.5 control boards stucks after some command pySerial.flushInput() # Delete what is still inside the buffer # These functions shall run in separate threads # recv_thr() is used to catch sensor data def recv_thr(): global client_adr ser_line = "" try: while pySerial.readable(): # Lock while data in queue to get red THR_LOCK.acquire() while pySerial.inWaiting() > 0: # Remove newline character '\n' ser_line = pySerial.readline().strip() try: p = json.loads(ser_line) except (ValueError, KeyError, TypeError): # Print everything what is not a valid JSON string to console print ("JSON format error: %s" % ser_line) # Send the string to the client after is was flagged nojson_line = '{"type":"NOJSON","data":"%s"}' % ser_line if client_adr != "": bytes = udp_sock.sendto(nojson_line, client_adr) else: logging.info(ser_line) if client_adr != "": bytes = udp_sock.sendto(ser_line, client_adr) THR_LOCK.release() # Terminate process (makes restarting in the init.d part possible) except: os.kill(os.getpid(), 15) # trnm_thr() sends commands to APM2.5 def trnm_thr(): global client_adr msg = "" try: while pySerial.writable(): try: # Wait for UDP packet from ground station msg, client_adr = udp_sock.recvfrom(512) except socket.timeout: # Log the problem logging.error("Read timeout on socket '{}': {}".format(adr, e)) else: try: # parse JSON string from socket p = json.loads(msg) except (ValueError, KeyError, TypeError): logging.debug("JSON format error: " + msg.strip() ) else: # remote control is about controlling the model (thrust and attitude) if p['type'] == 'rc': com = "%d,%d,%d,%d" % (p['r'], p['p'], p['t'], p['y']) THR_LOCK.acquire() send_data("RC#", com) THR_LOCK.release() # Add a waypoint if p['type'] == 'uav': com = "%d,%d,%d,%d" % (p['lat_d'], p['lon_d'], p['alt_m'], p['flag_t'] ) THR_LOCK.acquire() send_data("UAV#", com) THR_LOCK.release() # PID config is about to change the sensitivity of the model to changes in attitude if p['type'] == 'pid': com = "%.2f,%.2f,%.4f,%.2f;%.2f,%.2f,%.4f,%.2f;%.2f,%.2f,%.4f,%.2f;%.2f,%.2f,%.4f,%.2f;%.2f,%.2f,%.4f,%.2f;%.2f,%.2f,%.2f,%.2f,%.2f" % ( p['p_rkp'], p['p_rki'], p['p_rkd'], p['p_rimax'], p['r_rkp'], p['r_rki'], p['r_rkd'], p['r_rimax'], p['y_rkp'], p['y_rki'], p['y_rkd'], p['y_rimax'], p['t_rkp'], p['t_rki'], p['t_rkd'], p['t_rimax'], p['a_rkp'], p['a_rki'], p['a_rkd'], p['a_rimax'], p['p_skp'], p['r_skp'], p['y_skp'], p['t_skp'], p['a_skp'] ) THR_LOCK.acquire() send_data("PID#", com) THR_LOCK.release() # This section is about correcting drifts while model is flying (e.g. due to imbalances of the model) if p['type'] == 'cmp': com = "%.2f,%.2f" % (p['r'], p['p']) THR_LOCK.acquire() send_data("CMP#", com) THR_LOCK.release() # With this section you may start the calibration of the gyro again if p['type'] == 'gyr': com = "%d" % (p['cal']) THR_LOCK.acquire() send_data("GYR#", com) THR_LOCK.release() # Ping service for calculating the latency of the connection if p['type'] == 'ping': com = '{"type":"pong","v":%d}' % (p['v']) if client_adr != "": bytes = udp_sock.sendto(com, client_adr) # User interactant for gyrometer calibration if p['type'] == 'user_interactant': THR_LOCK.acquire() bytes = pySerial.write("x") # write a char into the serial device pySerial.flushInput() THR_LOCK.release() # Terminate process (makes restarting in the init.d part possible) except: os.kill(os.getpid(), 15) # Main program for sending and receiving # Working with two separate threads def main(): # Start threads for receiving and transmitting recv=threading.Thread(target=recv_thr) trnm=threading.Thread(target=trnm_thr) recv.start() trnm.start() # Start Program bInitialized, pySerial = init_serial() if not bInitialized: print ("Could not open any serial port. Exit script.") sys.exit(0) main()
stdout
\$\endgroup\$