Skip to content

Commit 98ef270

Browse files
Added Python Datalogger and Arduino Side Code to repo
1 parent ff5a9fa commit 98ef270

File tree

2 files changed

+354
-0
lines changed

2 files changed

+354
-0
lines changed
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
// Arduino side code for data acquisition
2+
// Assume Arduino connected to 4 Sensors ,On receiving appropriate characters,Arduino sends back Sensor values
3+
4+
// @ -> will trigger TransmitHumidityValue(); -> will send back humidity value
5+
// # -> will trigger TransmitSoilMoistureValue(); -> will send back Soil-Moisture value
6+
// $ -> will trigger Transmit_Temperature_Value(); -> will send back Temperature Value
7+
// & -> will trigger TransmitLightIntensityValue(); -> will send back Light Sensor Value
8+
9+
// Functions are standin and user is supposed to implement them according to their needs
10+
// For Demo, the functions send a random number.
11+
12+
13+
void setup()
14+
{
15+
Serial.begin(9600); //Data will be send to PC @9600bps
16+
}
17+
18+
void loop()
19+
{
20+
char ReceivedByte; //
21+
22+
if (Serial.available() > 0) //Wait for data reception
23+
{
24+
ReceivedByte = Serial.read();//Read data from Arduino Serial UART buffer
25+
26+
switch(ReceivedByte)
27+
{
28+
case '@': //Serial.print('@');
29+
//Serial.print("HumidityValue ->");
30+
TransmitHumidityValue();
31+
break;
32+
case '$': //Serial.print('$');
33+
//Serial.print("4TemperatureValues ->");
34+
Transmit_Temperature_Value();
35+
break;
36+
case '#': //Serial.print('#');
37+
//Serial.print("SoilMoistureValue ->");
38+
TransmitSoilMoistureValue();
39+
break;
40+
case '&': //Serial.print('&');
41+
//Serial.print("LightIntensityValue -> ");
42+
TransmitLightIntensityValue();
43+
break;
44+
default: Serial.println("Default Value");
45+
}//end of switch()
46+
}//end of if
47+
}//end of loop()
48+
49+
50+
51+
void TransmitHumidityValue(void)
52+
{
53+
//Send Dummy Values using Random number generator for now
54+
//Implement the code for talking with sensor here
55+
56+
long RandomNumber ;
57+
RandomNumber = random(40,90); //Generate Random number between 40 and 90
58+
Serial.println(RandomNumber); //Send Number followed by \n so python readline will exit on PC side
59+
60+
}
61+
62+
void TransmitSoilMoistureValue(void)
63+
{
64+
// Send Dummy Values using Random number generator for now
65+
// Implement the code for talking with sensor here
66+
long RandomNumber ;
67+
RandomNumber = random(30,50);// Generate Random number between 30 and 50
68+
Serial.println(RandomNumber);// Send Number followed by \n so python readline will exit on PC side
69+
}
70+
71+
void TransmitLightIntensityValue(void)
72+
{
73+
// Send Dummy Values using Random number generator for now
74+
// Implement the code for talking with sensor here
75+
76+
long RandomNumber ;
77+
RandomNumber = random(0,1024);// Generate Random number between 0 and 1024
78+
Serial.println(RandomNumber); // Send Number followed by \n so python readline will exit on PC side
79+
}
80+
81+
82+
void Transmit_Temperature_Value(void)
83+
{
84+
// Send Dummy Values using Random number generator for now
85+
// Implement the code for talking with sensor here
86+
87+
long RandomNumber ;
88+
RandomNumber = random(20,90);// Generate Random number between 20 and 90
89+
Serial.println(RandomNumber);// Send Number followed by \n so python readline will exit on PC side
90+
91+
}
Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
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

Comments
 (0)