|
| 1 | +#requires pyserial installed |
| 2 | +#requires ttkbootstrap installed |
| 3 | + |
| 4 | +from tkinter import * |
| 5 | +import ttkbootstrap as ttkb |
| 6 | +from ttkbootstrap.dialogs import Messagebox |
| 7 | +from ttkbootstrap.scrolled import ScrolledText |
| 8 | +import tkinter as tk |
| 9 | + |
| 10 | + |
| 11 | +import serial |
| 12 | +import threading |
| 13 | +import time |
| 14 | +import csv |
| 15 | +import webbrowser |
| 16 | +import os |
| 17 | + |
| 18 | + |
| 19 | +baud_rate = 0 |
| 20 | + |
| 21 | +csv_delimiter = ',' |
| 22 | +log_int = 0 |
| 23 | + |
| 24 | + |
| 25 | +#create Dropdown options |
| 26 | +baudrates = [0,600,1200,2400,4800,9600,19200,38400] |
| 27 | +log_interval = [0,0.5,1,1.5,2,2.5,3,4,5,10,20,30,60,120] |
| 28 | + |
| 29 | +tutorial_text = ''' Tutorial |
| 30 | + |
| 31 | + Select Serial Port number like COM3 or COM12 in Windows |
| 32 | + Select Serial Port number like ttyUSB0 or ttyACM0 in Linux |
| 33 | + Select Baudrate from Drop Down Menu |
| 34 | + Select Logging interval from Drop Down Menu |
| 35 | + |
| 36 | + Click Start Logging to start datalog |
| 37 | + Click Stop Logging to stop datalog |
| 38 | + |
| 39 | + Data saved in disk\n''' |
| 40 | + |
| 41 | +def acquire_arduino_data(serialport_name,baud_rate,logging_interval): |
| 42 | + |
| 43 | + serialport_obj = None #declared here so accessible inside and outside try/catch |
| 44 | + log_count = 1 |
| 45 | + |
| 46 | + |
| 47 | + |
| 48 | + #Create the Serial port object |
| 49 | + try: |
| 50 | + serialport_obj = serial.Serial(serialport_name,baud_rate) #open the serial port |
| 51 | + text_log.insert(END,f'{serialport_name} selected,at {baud_rate} ,\n') |
| 52 | + text_log.insert(END,f'Logging Interval = {logging_interval} seconds,\n') |
| 53 | + text_log.insert(END,f'Wait for Arduino to Reset,\n') |
| 54 | + time.sleep(2) #Some time for Arduino board to reset |
| 55 | + text_log.insert(END,f'Arduino Ready Now ,\n\n') |
| 56 | + |
| 57 | + except serial.SerialException as var : #In case of error |
| 58 | + text_log.insert(END,f'{var} ,\n') |
| 59 | + |
| 60 | + log_file_name = create_filename_current_date_time() |
| 61 | + |
| 62 | + |
| 63 | + |
| 64 | + csv_header =['No','Date','Time','Unix Time','Humidity','Soil Moisture','Temperature','Light Intensity'] |
| 65 | + |
| 66 | + with open(log_file_name,'a',newline ='') as File_obj: |
| 67 | + csvwriter_obj = csv.writer(File_obj, delimiter = csv_delimiter) |
| 68 | + csvwriter_obj.writerow(csv_header) |
| 69 | + |
| 70 | + text_log.insert(END,f'Log file -> {log_file_name}\n\n') |
| 71 | + text_log.insert(END,f'Starting Logging @{logging_interval} interval\n\n') |
| 72 | + text_log.insert(END,f'{csv_header}\n') |
| 73 | + |
| 74 | + csv_filename_loc.insert(END,f'{os.getcwd()}\n') |
| 75 | + csv_filename_loc.insert(END,f'{log_file_name}\n') |
| 76 | + |
| 77 | + |
| 78 | + while True: |
| 79 | + |
| 80 | + #print(start_logging_event.is_set()) |
| 81 | + if start_logging_event.is_set() == True: |
| 82 | + |
| 83 | + arduino_sensor_data_list = read_arduino_sensors(serialport_obj) |
| 84 | + #print(arduino_sensor_data_list) |
| 85 | + unix_timestamp = int(time.time()) #get current unix time to time stamp data |
| 86 | + |
| 87 | + log_time_date = time.localtime(unix_timestamp) #Convert epoch time to human readable time,date format |
| 88 | + log_time = time.strftime("%H:%M:%S",log_time_date) #hh:mm:ss |
| 89 | + log_date = time.strftime("%d %B %Y",log_time_date) #dd MonthName Year |
| 90 | + |
| 91 | + |
| 92 | + |
| 93 | + arduino_sensor_data_list.insert(0,str(log_count)) |
| 94 | + arduino_sensor_data_list.insert(1,str(log_date)) |
| 95 | + arduino_sensor_data_list.insert(2,str(log_time)) |
| 96 | + arduino_sensor_data_list.insert(3,str(unix_timestamp)) |
| 97 | + |
| 98 | + #print(arduino_sensor_data_list) |
| 99 | + text_log.insert(END,f'{arduino_sensor_data_list} ,\n') |
| 100 | + text_log.see(tk.END) #for auto scrolling |
| 101 | + |
| 102 | + with open(log_file_name,'a',newline='') as File_obj: |
| 103 | + csvwriter_obj = csv.writer(File_obj, delimiter = csv_delimiter) |
| 104 | + csvwriter_obj.writerow(arduino_sensor_data_list) |
| 105 | + |
| 106 | + #print('data acquiring') |
| 107 | + log_count = log_count + 1 |
| 108 | + |
| 109 | + time.sleep(logging_interval) |
| 110 | + |
| 111 | + |
| 112 | + elif start_logging_event.is_set() != True: |
| 113 | + serialport_obj.close() |
| 114 | + text_log.insert(END,f'+==================================================+ \n') |
| 115 | + text_log.insert(END,f'Logging Ended \n') |
| 116 | + break |
| 117 | + |
| 118 | + |
| 119 | + |
| 120 | + |
| 121 | + |
| 122 | + |
| 123 | +def read_arduino_sensors(serialport_obj): |
| 124 | + |
| 125 | + return_list =[0,0,0,0] |
| 126 | + |
| 127 | + polling_interval = 0.010 #In seconds,to give time for arduino to respond |
| 128 | + |
| 129 | + serialport_obj.write(b'@') |
| 130 | + time.sleep(polling_interval) |
| 131 | + humidity_value = serialport_obj.readline() |
| 132 | + humidity_value = humidity_value.strip() |
| 133 | + |
| 134 | + serialport_obj.write(b'#') |
| 135 | + time.sleep(polling_interval) |
| 136 | + soil_value = serialport_obj.readline() |
| 137 | + soil_value = soil_value.strip() |
| 138 | + |
| 139 | + serialport_obj.write(b'$') |
| 140 | + time.sleep(polling_interval) |
| 141 | + temp_value = serialport_obj.readline() |
| 142 | + temp_value = temp_value.strip() |
| 143 | + |
| 144 | + serialport_obj.write(b'&') |
| 145 | + time.sleep(polling_interval) |
| 146 | + light_value = serialport_obj.readline() |
| 147 | + light_value = light_value.strip() |
| 148 | + |
| 149 | + #print(humidity_value,soil_value,temp_value,light_value) |
| 150 | + |
| 151 | + return_list[0] = humidity_value.decode() |
| 152 | + return_list[1] = soil_value.decode() |
| 153 | + return_list[2] = temp_value.decode() |
| 154 | + return_list[3] = light_value.decode() |
| 155 | + |
| 156 | + return return_list |
| 157 | + |
| 158 | +def create_filename_current_date_time(): |
| 159 | + # Generate file name using Current Date and Time |
| 160 | + current_local_time = time.localtime() #Get Current date time |
| 161 | + filename = time.strftime("%d_%B_%Y_%Hh_%Mm_%Ss",current_local_time)# 24hour clock format |
| 162 | + filename = 'ard_'+ filename + '_daq_log.csv' |
| 163 | + #print(f'\nCreated Log File -> {filename}') |
| 164 | + return filename |
| 165 | + |
| 166 | + |
| 167 | +def tutorial_btn_handler(): |
| 168 | + webbrowser.open_new(r'https://www.xanthium.in/multithreading-serial-port-data-acquisition-to-csv-file-using-producer-consumer-pattern-python') |
| 169 | + |
| 170 | + |
| 171 | + |
| 172 | +def start_log_btn_handler(): |
| 173 | + start_logging_event.set() |
| 174 | + serialport_name = port_no_entry.get() |
| 175 | + t1 = threading.Thread(target = acquire_arduino_data,args=(serialport_name,baud_rate,log_int)) |
| 176 | + t1.start() |
| 177 | + |
| 178 | + |
| 179 | +def stop_log_btn_handler(): |
| 180 | + start_logging_event.clear() |
| 181 | + |
| 182 | + |
| 183 | + |
| 184 | +def on_select_option_bind_baudrates(e): |
| 185 | + global baud_rate |
| 186 | + baud_rate = int(baud_rates_combo_box.get()) |
| 187 | + #print(baud_rate) |
| 188 | + |
| 189 | +def on_select_option_bind_log_interval(e): |
| 190 | + global log_int |
| 191 | + log_int = float(log_interval_combo_box.get()) |
| 192 | + print(log_int) |
| 193 | + |
| 194 | + |
| 195 | + |
| 196 | + |
| 197 | +start_logging_event = threading.Event() |
| 198 | + |
| 199 | +# Main Window Creation |
| 200 | +root = ttkb.Window(themename = 'superhero') # theme = superhero |
| 201 | +root.title('SerialPort Datalogging to CSV file') |
| 202 | +root.geometry('650x660') # Width X Height |
| 203 | + |
| 204 | +#Labels for naming boxes |
| 205 | +head_label = ttkb.Label(text = 'Python Serial Port CSV Datalogger',font = ('Helvetica',15),bootstyle='light') |
| 206 | +head_label.place(x=95,y=10) |
| 207 | + |
| 208 | +website_label = ttkb.Label(text = 'www.xanthium.in',font = ('Helvetica',10),bootstyle='warning') |
| 209 | +website_label.place(x=230,y=45) |
| 210 | + |
| 211 | +serialport_label = ttkb.Label(text = 'Select Port',bootstyle = 'light') |
| 212 | +serialport_label.place(x=20,y=90) |
| 213 | + |
| 214 | +baudrate_label = ttkb.Label(text = 'Baudrate',bootstyle = 'light') |
| 215 | +baudrate_label.place(x=220,y=90) |
| 216 | + |
| 217 | +lograte_label = ttkb.Label(text = 'Log Interval (Seconds)',bootstyle = 'light') |
| 218 | +lograte_label.place(x=440,y=90) |
| 219 | + |
| 220 | +#Create COM portEntry Widget |
| 221 | +port_no_entry = ttkb.Entry(root) |
| 222 | +port_no_entry.insert(0,'COMx') |
| 223 | +port_no_entry.place(x=20,y=120) |
| 224 | + |
| 225 | +#create Combobox for baudrates |
| 226 | +baud_rates_combo_box = ttkb.Combobox(values = baudrates) |
| 227 | +baud_rates_combo_box.place(x=220,y=120) |
| 228 | +baud_rates_combo_box.current(0)#set the default value on combobox |
| 229 | +#bind the combobox |
| 230 | +baud_rates_combo_box.bind('<<ComboboxSelected>>',on_select_option_bind_baudrates) |
| 231 | + |
| 232 | +#create Combobox for logging interval |
| 233 | +log_interval_combo_box = ttkb.Combobox(values = log_interval) |
| 234 | +log_interval_combo_box.place(x=440,y=120) |
| 235 | +log_interval_combo_box.current(0)#set the default value on combobox |
| 236 | +#bind the combobox |
| 237 | +log_interval_combo_box.bind('<<ComboboxSelected>>',on_select_option_bind_log_interval) |
| 238 | + |
| 239 | + |
| 240 | +#create button for controlling data acquisition and logging |
| 241 | +start_log_btn = ttkb.Button(text = 'Start Logging' ,command = start_log_btn_handler ).place(x=20, y=175) |
| 242 | +stop_log_btn = ttkb.Button(text = 'Stop Logging' ,command = stop_log_btn_handler ).place(x=220,y=175) |
| 243 | +tutorial_btn = ttkb.Button(text = 'Online Web Tutorial' ,command = tutorial_btn_handler ).place(x=440,y=175) |
| 244 | + |
| 245 | +# Scrollable text box for CSV file name |
| 246 | + |
| 247 | +file_loc_label = ttkb.Label(text = 'CSV file Name and Location',bootstyle = 'light').place(x=20,y=225) |
| 248 | + |
| 249 | +csv_filename_loc = ScrolledText(root,height=2,width=74,wrap = WORD,autohide=False,) |
| 250 | +csv_filename_loc.place(x=20,y=250) |
| 251 | + |
| 252 | +# Define the font family and font size |
| 253 | +font_family = 'Helvetica' |
| 254 | +font_size = 10 |
| 255 | + |
| 256 | +log_label = ttkb.Label(text = 'Logging',bootstyle = 'light').place(x=20,y=318) |
| 257 | + |
| 258 | +text_log = ScrolledText(root,height=15,width=66,wrap = WORD,autohide=False,font=(font_family, font_size)) |
| 259 | +text_log.place(x=20,y=340) |
| 260 | +text_log.insert(END,tutorial_text) |
| 261 | + |
| 262 | + |
| 263 | +root.mainloop() |
0 commit comments