1- #requires pyserial installed
2- #requires ttkbootstrap installed
1+ '''
2+ GUI Tkinter (ttkbootstrap) program to read data from arduino or DAQ and log into a CSV file.
3+ CSV file name automatically generated using current date and time
34
5+
6+ requires: pyserial installed
7+ : ttkbootstrap installed
8+
9+ Author : Rahul.S
10+
11+ (c) www.xanthium.in (2024)
12+
13+
14+ #============================ Very Basic Overview of the Code ==========================================#
15+
16+ #below function runs inside a thread,all action takes place inside this
17+
18+ def acquire_arduino_data(serialport_name,baud_rate,logging_interval):
19+ #(log_count) logging count = 1
20+ #Create the Serial port object inside try catch
21+ #Create a file name using Current Date and time,create_filename_current_date_time()
22+ #Create CSV file,Write header to CSV file
23+
24+ while True:
25+
26+ if start_logging_event.is_set() == True:
27+ # Read Data from Arduino using SerialPort,read_arduino_sensors(serialport_obj)
28+ # Get time stamp using time.time()
29+ # Create a List to write to CSV file
30+ # Write list to CSV file
31+ # Wait here, based on logging_interval
32+ #increment logging count (log_count = log_count +1)
33+
34+ elif start_logging_event.is_set() == False:
35+ # Close SerialPort
36+ # break ,Exit from While loop
37+
38+
39+
40+
41+ start_logging_event set by def start_log_btn_handler(): of start_log_btn
42+ start_logging_event cleared by def stop_log_btn_handler(): of stop_log_btn
43+
44+ '''
45+
46+ #GUI Library Imports for Tkinter and ttkbootsrap
447from tkinter import *
548import ttkbootstrap as ttkb
649from ttkbootstrap .dialogs import Messagebox
750from ttkbootstrap .scrolled import ScrolledText
851import tkinter as tk
952
1053
11- import serial
12- import threading
13- import time
14- import csv
15- import webbrowser
16- import os
54+ import serial # for serial communication
55+ import threading # for creating threads
56+ import time # for delays and getting timestamps for data logging
57+ import csv # creating CSV files
58+ import webbrowser # for opening tutorial webpage using button in a web browser
59+ import os # for getting current working directory
1760
1861
19- baud_rate = 0
20-
21- csv_delimiter = ','
22- log_int = 0
62+ baud_rate = 0
63+ csv_delimiter = ',' # changing this would change the delimiter in CSV file Eg : ; |
64+ log_int = 0 # logging interval global value
2365
2466
2567#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 ]
68+ baudrates = [0 ,600 ,1200 ,2400 ,4800 ,9600 ,19200 ,38400 ] # dropdown for standard baudrates
69+ log_interval = [0 ,0.5 ,1 ,1.5 ,2 ,2.5 ,3 ,4 ,5 ,10 ,20 ,30 ,60 ,120 ] # dropdown for logging interval in seconds,0 means maximum speed
70+
2871
2972tutorial_text = ''' Tutorial
3073
3881
3982 Data saved in disk\n '''
4083
84+
85+ #All Action takes place here
86+ #function to read data from arduino,create csv file,process button options
87+ #function runs inside thread t1
88+
4189def acquire_arduino_data (serialport_name ,baud_rate ,logging_interval ):
4290
4391 serialport_obj = None #declared here so accessible inside and outside try/catch
4492 log_count = 1
4593
46-
47-
94+
4895 #Create the Serial port object
4996 try :
5097 serialport_obj = serial .Serial (serialport_name ,baud_rate ) #open the serial port
98+
5199 text_log .insert (END ,f'{ serialport_name } selected,at { baud_rate } ,\n ' )
52100 text_log .insert (END ,f'Logging Interval = { logging_interval } seconds,\n ' )
53101 text_log .insert (END ,f'Wait for Arduino to Reset,\n ' )
102+
54103 time .sleep (2 ) #Some time for Arduino board to reset
104+
55105 text_log .insert (END ,f'Arduino Ready Now ,\n \n ' )
56106
57107 except serial .SerialException as var : #In case of error
108+
58109 text_log .insert (END ,f'{ var } ,\n ' )
59110
60- log_file_name = create_filename_current_date_time ()
61111
112+
113+ log_file_name = create_filename_current_date_time () # create a file name using current date and time
114+ # Eg ard_11_March_2024_07h_29m_00s_daq_log.csv
62115
63116
117+ #create CSV header to write once into the created file
118+
64119 csv_header = ['No' ,'Date' ,'Time' ,'Unix Time' ,'Humidity' ,'Soil Moisture' ,'Temperature' ,'Light Intensity' ]
65120
121+ # write csv header into the createdfile
66122 with open (log_file_name ,'a' ,newline = '' ) as File_obj :
67123 csvwriter_obj = csv .writer (File_obj , delimiter = csv_delimiter )
68124 csvwriter_obj .writerow (csv_header )
69-
125+
126+ #Write info into scrolled text box
70127 text_log .insert (END ,f'Log file -> { log_file_name } \n \n ' )
71128 text_log .insert (END ,f'Starting Logging @{ logging_interval } interval\n \n ' )
72129 text_log .insert (END ,f'{ csv_header } \n ' )
@@ -75,59 +132,84 @@ def acquire_arduino_data(serialport_name,baud_rate,logging_interval):
75132 csv_filename_loc .insert (END ,f'{ log_file_name } \n ' )
76133
77134
135+ #infinite loop,that constantly checks for (start_logging_event) status set or clear
136+ #(start_logging_event) controlled by buttons
78137 while True :
79138
80- #print(start_logging_event.is_set())
139+
81140 if start_logging_event .is_set () == True :
82141
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
142+ arduino_sensor_data_list = read_arduino_sensors (serialport_obj ) #communicate with arduino and get data
143+
144+ unix_timestamp = int (time .time ()) #get current unix time to time stamp data
86145
87146 log_time_date = time .localtime (unix_timestamp ) #Convert epoch time to human readable time,date format
88147 log_time = time .strftime ("%H:%M:%S" ,log_time_date ) #hh:mm:ss
89148 log_date = time .strftime ("%d %B %Y" ,log_time_date ) #dd MonthName Year
90149
91-
150+ #arduino_sensor_data_list[]
151+ #
152+ #arduino_sensor_data_list[0] = humidity_value
153+ #arduino_sensor_data_list[1] = soil_value
154+ #arduino_sensor_data_list[2] = temp_value
155+ #arduino_sensor_data_list[3] = light_value
92156
93157 arduino_sensor_data_list .insert (0 ,str (log_count ))
94158 arduino_sensor_data_list .insert (1 ,str (log_date ))
95159 arduino_sensor_data_list .insert (2 ,str (log_time ))
96160 arduino_sensor_data_list .insert (3 ,str (unix_timestamp ))
97161
162+ #arduino_sensor_data_list[] after insert()
163+ #
164+ #arduino_sensor_data_list[0] = log_count
165+ #arduino_sensor_data_list[1] = log_date
166+ #arduino_sensor_data_list[3] = log_time
167+ #arduino_sensor_data_list[4] = light_value
168+ #arduino_sensor_data_list[5] = unix_timestamp
169+ #arduino_sensor_data_list[6] = soil_value
170+ #arduino_sensor_data_list[7] = temp_value
171+ #arduino_sensor_data_list[8] = light_value
172+ #
173+ #makes it easier to write into CSV file
174+
98175 #print(arduino_sensor_data_list)
176+
99177 text_log .insert (END ,f'{ arduino_sensor_data_list } ,\n ' )
100178 text_log .see (tk .END ) #for auto scrolling
101179
180+ #write arduino_sensor_data_list to CSV file
181+
102182 with open (log_file_name ,'a' ,newline = '' ) as File_obj :
103183 csvwriter_obj = csv .writer (File_obj , delimiter = csv_delimiter )
104- csvwriter_obj .writerow (arduino_sensor_data_list )
184+ csvwriter_obj .writerow (arduino_sensor_data_list ) # write one row
105185
106- #print('data acquiring')
107- log_count = log_count + 1
186+
187+ log_count = log_count + 1 #increment logging count
108188
109189 time .sleep (logging_interval )
110190
111191
112- elif start_logging_event .is_set () != True :
192+ elif start_logging_event .is_set () == False :
193+
113194 serialport_obj .close ()
114195 text_log .insert (END ,f'+==================================================+ \n ' )
115196 text_log .insert (END ,f'Logging Ended \n ' )
116- break
197+
198+ break # exit from infinite loop
117199
118200
119201
120202
121203
122-
204+ #Function sends and receive data from arduino connected to PC
123205def read_arduino_sensors (serialport_obj ):
124206
125207 return_list = [0 ,0 ,0 ,0 ]
126208
127209 polling_interval = 0.010 #In seconds,to give time for arduino to respond
128210
129- serialport_obj .write (b'@' )
130- time .sleep (polling_interval )
211+ serialport_obj .write (b'@' ) #Send @ to Arduino to get humidity value
212+ time .sleep (polling_interval ) #wait some time,to give arduino to respond
131213 humidity_value = serialport_obj .readline ()
132214 humidity_value = humidity_value .strip ()
133215
@@ -148,11 +230,18 @@ def read_arduino_sensors(serialport_obj):
148230
149231 #print(humidity_value,soil_value,temp_value,light_value)
150232
151- return_list [0 ] = humidity_value .decode ()
233+ return_list [0 ] = humidity_value .decode () #.decode is used to remove the byte 'b',convert to string
152234 return_list [1 ] = soil_value .decode ()
153235 return_list [2 ] = temp_value .decode ()
154236 return_list [3 ] = light_value .decode ()
155237
238+ #return_list[]
239+ #
240+ #return_list[0] = humidity_value
241+ #return_list[1] = soil_value
242+ #return_list[2] = temp_value
243+ #return_list[3] = light_value
244+
156245 return return_list
157246
158247def create_filename_current_date_time ():
@@ -164,6 +253,8 @@ def create_filename_current_date_time():
164253 return filename
165254
166255
256+ #========================== Button Handlers ================================#
257+
167258def tutorial_btn_handler ():
168259 webbrowser .open_new (r'https://www.xanthium.in/multithreading-serial-port-data-acquisition-to-csv-file-using-producer-consumer-pattern-python' )
169260
@@ -180,6 +271,7 @@ def stop_log_btn_handler():
180271 start_logging_event .clear ()
181272
182273
274+ #========================== Drop Down Menu Handlers ================================#
183275
184276def on_select_option_bind_baudrates (e ):
185277 global baud_rate
@@ -189,14 +281,14 @@ def on_select_option_bind_baudrates(e):
189281def on_select_option_bind_log_interval (e ):
190282 global log_int
191283 log_int = float (log_interval_combo_box .get ())
192- print (log_int )
284+ # print(log_int)
193285
194286
195287
196-
197- start_logging_event = threading .Event ()
288+ start_logging_event = threading .Event () #Event Creation
289+
290+ #============================== Main Window Creation=======================================#
198291
199- # Main Window Creation
200292root = ttkb .Window (themename = 'superhero' ) # theme = superhero
201293root .title ('SerialPort Datalogging to CSV file' )
202294root .geometry ('650x660' ) # Width X Height
0 commit comments