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
3
4
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
4
47
from tkinter import *
5
48
import ttkbootstrap as ttkb
6
49
from ttkbootstrap .dialogs import Messagebox
7
50
from ttkbootstrap .scrolled import ScrolledText
8
51
import tkinter as tk
9
52
10
53
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
17
60
18
61
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
23
65
24
66
25
67
#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
+
28
71
29
72
tutorial_text = ''' Tutorial
30
73
38
81
39
82
Data saved in disk\n '''
40
83
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
+
41
89
def acquire_arduino_data (serialport_name ,baud_rate ,logging_interval ):
42
90
43
91
serialport_obj = None #declared here so accessible inside and outside try/catch
44
92
log_count = 1
45
93
46
-
47
-
94
+
48
95
#Create the Serial port object
49
96
try :
50
97
serialport_obj = serial .Serial (serialport_name ,baud_rate ) #open the serial port
98
+
51
99
text_log .insert (END ,f'{ serialport_name } selected,at { baud_rate } ,\n ' )
52
100
text_log .insert (END ,f'Logging Interval = { logging_interval } seconds,\n ' )
53
101
text_log .insert (END ,f'Wait for Arduino to Reset,\n ' )
102
+
54
103
time .sleep (2 ) #Some time for Arduino board to reset
104
+
55
105
text_log .insert (END ,f'Arduino Ready Now ,\n \n ' )
56
106
57
107
except serial .SerialException as var : #In case of error
108
+
58
109
text_log .insert (END ,f'{ var } ,\n ' )
59
110
60
- log_file_name = create_filename_current_date_time ()
61
111
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
62
115
63
116
117
+ #create CSV header to write once into the created file
118
+
64
119
csv_header = ['No' ,'Date' ,'Time' ,'Unix Time' ,'Humidity' ,'Soil Moisture' ,'Temperature' ,'Light Intensity' ]
65
120
121
+ # write csv header into the createdfile
66
122
with open (log_file_name ,'a' ,newline = '' ) as File_obj :
67
123
csvwriter_obj = csv .writer (File_obj , delimiter = csv_delimiter )
68
124
csvwriter_obj .writerow (csv_header )
69
-
125
+
126
+ #Write info into scrolled text box
70
127
text_log .insert (END ,f'Log file -> { log_file_name } \n \n ' )
71
128
text_log .insert (END ,f'Starting Logging @{ logging_interval } interval\n \n ' )
72
129
text_log .insert (END ,f'{ csv_header } \n ' )
@@ -75,59 +132,84 @@ def acquire_arduino_data(serialport_name,baud_rate,logging_interval):
75
132
csv_filename_loc .insert (END ,f'{ log_file_name } \n ' )
76
133
77
134
135
+ #infinite loop,that constantly checks for (start_logging_event) status set or clear
136
+ #(start_logging_event) controlled by buttons
78
137
while True :
79
138
80
- #print(start_logging_event.is_set())
139
+
81
140
if start_logging_event .is_set () == True :
82
141
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
86
145
87
146
log_time_date = time .localtime (unix_timestamp ) #Convert epoch time to human readable time,date format
88
147
log_time = time .strftime ("%H:%M:%S" ,log_time_date ) #hh:mm:ss
89
148
log_date = time .strftime ("%d %B %Y" ,log_time_date ) #dd MonthName Year
90
149
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
92
156
93
157
arduino_sensor_data_list .insert (0 ,str (log_count ))
94
158
arduino_sensor_data_list .insert (1 ,str (log_date ))
95
159
arduino_sensor_data_list .insert (2 ,str (log_time ))
96
160
arduino_sensor_data_list .insert (3 ,str (unix_timestamp ))
97
161
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
+
98
175
#print(arduino_sensor_data_list)
176
+
99
177
text_log .insert (END ,f'{ arduino_sensor_data_list } ,\n ' )
100
178
text_log .see (tk .END ) #for auto scrolling
101
179
180
+ #write arduino_sensor_data_list to CSV file
181
+
102
182
with open (log_file_name ,'a' ,newline = '' ) as File_obj :
103
183
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
105
185
106
- #print('data acquiring')
107
- log_count = log_count + 1
186
+
187
+ log_count = log_count + 1 #increment logging count
108
188
109
189
time .sleep (logging_interval )
110
190
111
191
112
- elif start_logging_event .is_set () != True :
192
+ elif start_logging_event .is_set () == False :
193
+
113
194
serialport_obj .close ()
114
195
text_log .insert (END ,f'+==================================================+ \n ' )
115
196
text_log .insert (END ,f'Logging Ended \n ' )
116
- break
197
+
198
+ break # exit from infinite loop
117
199
118
200
119
201
120
202
121
203
122
-
204
+ #Function sends and receive data from arduino connected to PC
123
205
def read_arduino_sensors (serialport_obj ):
124
206
125
207
return_list = [0 ,0 ,0 ,0 ]
126
208
127
209
polling_interval = 0.010 #In seconds,to give time for arduino to respond
128
210
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
131
213
humidity_value = serialport_obj .readline ()
132
214
humidity_value = humidity_value .strip ()
133
215
@@ -148,11 +230,18 @@ def read_arduino_sensors(serialport_obj):
148
230
149
231
#print(humidity_value,soil_value,temp_value,light_value)
150
232
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
152
234
return_list [1 ] = soil_value .decode ()
153
235
return_list [2 ] = temp_value .decode ()
154
236
return_list [3 ] = light_value .decode ()
155
237
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
+
156
245
return return_list
157
246
158
247
def create_filename_current_date_time ():
@@ -164,6 +253,8 @@ def create_filename_current_date_time():
164
253
return filename
165
254
166
255
256
+ #========================== Button Handlers ================================#
257
+
167
258
def tutorial_btn_handler ():
168
259
webbrowser .open_new (r'https://www.xanthium.in/multithreading-serial-port-data-acquisition-to-csv-file-using-producer-consumer-pattern-python' )
169
260
@@ -180,6 +271,7 @@ def stop_log_btn_handler():
180
271
start_logging_event .clear ()
181
272
182
273
274
+ #========================== Drop Down Menu Handlers ================================#
183
275
184
276
def on_select_option_bind_baudrates (e ):
185
277
global baud_rate
@@ -189,14 +281,14 @@ def on_select_option_bind_baudrates(e):
189
281
def on_select_option_bind_log_interval (e ):
190
282
global log_int
191
283
log_int = float (log_interval_combo_box .get ())
192
- print (log_int )
284
+ # print(log_int)
193
285
194
286
195
287
196
-
197
- start_logging_event = threading .Event ()
288
+ start_logging_event = threading .Event () #Event Creation
289
+
290
+ #============================== Main Window Creation=======================================#
198
291
199
- # Main Window Creation
200
292
root = ttkb .Window (themename = 'superhero' ) # theme = superhero
201
293
root .title ('SerialPort Datalogging to CSV file' )
202
294
root .geometry ('650x660' ) # Width X Height
0 commit comments