|
| 1 | +# Python + Arduino-based Radar Plotter |
| 2 | +# |
| 3 | +# ** Works with any motor that outputs angular rotation |
| 4 | +# ** and with any distance sensor (HC-SR04, VL53L0x,LIDAR) |
| 5 | +# |
| 6 | +import numpy as np |
| 7 | +import matplotlib |
| 8 | +matplotlib.use('TkAgg') |
| 9 | +import matplotlib.pyplot as plt |
| 10 | +from matplotlib.widgets import Button |
| 11 | +import serial,sys,glob |
| 12 | +import serial.tools.list_ports as COMs |
| 13 | +# |
| 14 | +# |
| 15 | +############################################ |
| 16 | +# Find Arudino ports, select one, then start communication with it |
| 17 | +############################################ |
| 18 | +# |
| 19 | +def port_search(): |
| 20 | + if sys.platform.startswith('win'): # Windows |
| 21 | + ports = ['COM{0:1.0f}'.format(ii) for ii in range(1,256)] |
| 22 | + elif sys.platform.startswith('linux') or sys.platform.startswith('cygwin'): |
| 23 | + ports = glob.glob('/dev/tty[A-Za-z]*') |
| 24 | + elif sys.platform.startswith('darwin'): # MAC |
| 25 | + ports = glob.glob('/dev/tty.*') |
| 26 | + else: |
| 27 | + raise EnvironmentError('Machine Not pyserial Compatible') |
| 28 | + |
| 29 | + arduinos = [] |
| 30 | + for port in ports: # loop through to determine if accessible |
| 31 | + if len(port.split('Bluetooth'))>1: |
| 32 | + continue |
| 33 | + try: |
| 34 | + ser = serial.Serial(port) |
| 35 | + ser.close() |
| 36 | + arduinos.append(port) # if we can open it, consider it an arduino |
| 37 | + except (OSError, serial.SerialException): |
| 38 | + pass |
| 39 | + return arduinos |
| 40 | + |
| 41 | +arduino_ports = port_search() |
| 42 | +ser = serial.Serial(arduino_ports[0],baudrate=115200) # match baud on Arduino |
| 43 | +ser.flush() # clear the port |
| 44 | +# |
| 45 | +############################################ |
| 46 | +# Start the interactive plotting tool and |
| 47 | +# plot 180 degrees with dummy data to start |
| 48 | +############################################ |
| 49 | +# |
| 50 | +fig = plt.figure(facecolor='k') |
| 51 | +win = fig.canvas.manager.window # figure window |
| 52 | +screen_res = win.wm_maxsize() # used for window formatting later |
| 53 | +dpi = 150.0 # figure resolution |
| 54 | +fig.set_dpi(dpi) # set figure resolution |
| 55 | + |
| 56 | +# polar plot attributes and initial conditions |
| 57 | +ax = fig.add_subplot(111,polar=True,facecolor='#006d70') |
| 58 | +ax.set_position([-0.05,-0.05,1.1,1.05]) |
| 59 | +r_max = 100.0 # can change this based on range of sensor |
| 60 | +ax.set_ylim([0.0,r_max]) # range of distances to show |
| 61 | +ax.set_xlim([0.0,np.pi]) # limited by the servo span (0-180 deg) |
| 62 | +ax.tick_params(axis='both',colors='w') |
| 63 | +ax.grid(color='w',alpha=0.5) # grid color |
| 64 | +ax.set_rticks(np.linspace(0.0,r_max,5)) # show 5 different distances |
| 65 | +ax.set_thetagrids(np.linspace(0.0,180.0,10)) # show 10 angles |
| 66 | +angles = np.arange(0,181,1) # 0 - 180 degrees |
| 67 | +theta = angles*(np.pi/180.0) # to radians |
| 68 | +dists = np.ones((len(angles),)) # dummy distances until real data comes in |
| 69 | +pols, = ax.plot([],linestyle='',marker='o',markerfacecolor = 'w', |
| 70 | + markeredgecolor='#EFEFEF',markeredgewidth=1.0, |
| 71 | + markersize=10.0,alpha=0.9) # dots for radar points |
| 72 | +line1, = ax.plot([],color='w', |
| 73 | + linewidth=4.0) # sweeping arm plot |
| 74 | + |
| 75 | +# figure presentation adjustments |
| 76 | +fig.set_size_inches(0.96*(screen_res[0]/dpi),0.96*(screen_res[1]/dpi)) |
| 77 | +plot_res = fig.get_window_extent().bounds # window extent for centering |
| 78 | +win.wm_geometry('+{0:1.0f}+{1:1.0f}'.\ |
| 79 | + format((screen_res[0]/2.0)-(plot_res[2]/2.0), |
| 80 | + (screen_res[1]/2.0)-(plot_res[3]/2.0))) # centering plot |
| 81 | +fig.canvas.toolbar.pack_forget() # remove toolbar for clean presentation |
| 82 | +fig.canvas.set_window_title('Arduino Radar') |
| 83 | + |
| 84 | +fig.canvas.draw() # draw before loop |
| 85 | +axbackground = fig.canvas.copy_from_bbox(ax.bbox) # background to keep during loop |
| 86 | + |
| 87 | +############################################ |
| 88 | +# button event to stop program |
| 89 | +############################################ |
| 90 | + |
| 91 | +def stop_event(event): |
| 92 | + global stop_bool |
| 93 | + stop_bool = 1 |
| 94 | +prog_stop_ax = fig.add_axes([0.85,0.025,0.125,0.05]) |
| 95 | +pstop = Button(prog_stop_ax,'Stop Program',color='#FCFCFC',hovercolor='w') |
| 96 | +pstop.on_clicked(stop_event) |
| 97 | +# button to close window |
| 98 | +def close_event(event): |
| 99 | + global stop_bool,close_bool |
| 100 | + if stop_bool: |
| 101 | + plt.close('all') |
| 102 | + stop_bool = 1 |
| 103 | + close_bool = 1 |
| 104 | +close_ax = fig.add_axes([0.025,0.025,0.125,0.05]) |
| 105 | +close_but = Button(close_ax,'Close Plot',color='#FCFCFC',hovercolor='w') |
| 106 | +close_but.on_clicked(close_event) |
| 107 | + |
| 108 | +fig.show() |
| 109 | + |
| 110 | +############################################ |
| 111 | +# inifinite loop, constantly updating the |
| 112 | +# 180deg radar with incoming Arduino data |
| 113 | +############################################ |
| 114 | +# |
| 115 | +start_word,stop_bool,close_bool = False,False,False |
| 116 | +while True: |
| 117 | + try: |
| 118 | + if stop_bool: # stops program |
| 119 | + fig.canvas.toolbar.pack_configure() # show toolbar |
| 120 | + if close_bool: # closes radar window |
| 121 | + plt.close('all') |
| 122 | + break |
| 123 | + ser_bytes = ser.readline() # read Arduino serial data |
| 124 | + decoded_bytes = ser_bytes.decode('utf-8') # decode data to utf-8 |
| 125 | + data = (decoded_bytes.replace('\r','')).replace('\n','') |
| 126 | + if start_word: |
| 127 | + vals = [float(ii) for ii in data.split(',')] |
| 128 | + if len(vals)<2: |
| 129 | + continue |
| 130 | + angle,dist = vals # separate into angle and distance |
| 131 | + if dist>r_max: |
| 132 | + dist = 0.0 # measuring more than r_max, it's likely inaccurate |
| 133 | + dists[int(angle)] = dist |
| 134 | + if angle % 5 ==0: # update every 5 degrees |
| 135 | + pols.set_data(theta,dists) |
| 136 | + fig.canvas.restore_region(axbackground) |
| 137 | + ax.draw_artist(pols) |
| 138 | + |
| 139 | + line1.set_data(np.repeat((angle*(np.pi/180.0)),2), |
| 140 | + np.linspace(0.0,r_max,2)) |
| 141 | + ax.draw_artist(line1) |
| 142 | + |
| 143 | + fig.canvas.blit(ax.bbox) # replot only data |
| 144 | + fig.canvas.flush_events() # flush for next plot |
| 145 | + else: |
| 146 | + if data=='Radar Start': # stard word on Arduno |
| 147 | + start_word = True # wait for Arduino to output start word |
| 148 | + print('Radar Starting...') |
| 149 | + else: |
| 150 | + continue |
| 151 | + |
| 152 | + except KeyboardInterrupt: |
| 153 | + plt.close('all') |
| 154 | + print('Keyboard Interrupt') |
| 155 | + break |
0 commit comments