Skip to content

Commit 08ea62a

Browse files
committed
added provision for external sync using TCP-IP messaging
1 parent ceb2571 commit 08ea62a

File tree

6 files changed

+165
-33
lines changed

6 files changed

+165
-33
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ ui_form.py
66

77
# Data files
88
data**/**
9-
temp.csv
9+
**temp.csv
1010

1111
# C extensions
1212
*.so

exp_params.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@
2020
},
2121
"external_sync":
2222
{
23-
"enable": true,
23+
"enable": false,
2424
"role": "server",
2525
"tcp_ip": "localhost",
26-
"tcp_port":502
26+
"tcp_port":1025
2727
}
2828
}

exp_params_client.json

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"exp":
3+
{
4+
"study_name": "S1",
5+
"conditions": ["baseline", "mathEasy", "mathDifficult", "movement"],
6+
"channels": ["EDA", "Resp", "PPG Finger", "PPG Ear"],
7+
"channel_types": ["eda", "resp", "ppg", "ppg"],
8+
"channel_plot_colors": ["b", "g", "r", "m"]
9+
},
10+
"acq_params":
11+
{
12+
"fs":250.0,
13+
"baudrate":2000000,
14+
"timed_acquisition": true,
15+
"max_time_seconds": 30
16+
},
17+
"common":
18+
{
19+
"datapath":"data"
20+
},
21+
"external_sync":
22+
{
23+
"enable": true,
24+
"role": "client",
25+
"tcp_ip": "localhost",
26+
"tcp_port":1025
27+
}
28+
}

exp_params_server.json

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"exp":
3+
{
4+
"study_name": "S1",
5+
"conditions": ["baseline", "mathEasy", "mathDifficult", "movement"],
6+
"channels": ["EDA", "Resp", "PPG Finger", "PPG Ear"],
7+
"channel_types": ["eda", "resp", "ppg", "ppg"],
8+
"channel_plot_colors": ["b", "g", "r", "m"]
9+
},
10+
"acq_params":
11+
{
12+
"fs":250.0,
13+
"baudrate":2000000,
14+
"timed_acquisition": true,
15+
"max_time_seconds": 30
16+
},
17+
"common":
18+
{
19+
"datapath":"data"
20+
},
21+
"external_sync":
22+
{
23+
"enable": true,
24+
"role": "server",
25+
"tcp_ip": "localhost",
26+
"tcp_port":1025
27+
}
28+
}

main.py

Lines changed: 65 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import numpy as np
77
import csv
88
from datetime import datetime
9+
910
import numpy as np
1011

1112
from PySide6.QtWidgets import QApplication, QWidget, QGraphicsScene, QFileDialog
@@ -26,9 +27,10 @@
2627

2728
from utils.data_processing_lib import lFilter, lFilter_moving_average
2829
from utils.devices import serialPort
29-
from datetime import datetime
30+
from utils.external_sync import External_Sync
3031

31-
global live_acquisition_flag, hold_acquisition_thread, nChannels
32+
33+
global live_acquisition_flag, hold_acquisition_thread, nChannels, temp_filename
3234
live_acquisition_flag = False
3335
hold_acquisition_thread = True
3436
nChannels = 4
@@ -49,6 +51,7 @@ def load_ui(self):
4951
self.ui.exp_loaded = False
5052

5153
# Default params
54+
self.ui.ext_sync_flag = False
5255
self.ui.fs = 250 #sampling rate
5356
self.ui.baudrate = 2000000
5457

@@ -99,10 +102,14 @@ def load_ui(self):
99102
self.ui.listWidget_expConditions.currentItemChanged.connect(self.update_exp_condition)
100103

101104
self.myFig = None
102-
self.temp_filename = "temp.csv"
105+
106+
global temp_filename
107+
temp_utc_sec = str((datetime.now() - datetime(1970, 1, 1)).total_seconds())
108+
temp_utc_sec = temp_utc_sec.replace('.', '_')
109+
temp_filename = temp_utc_sec + "_temp.csv"
103110
self.csv_header = ['']
104111
self.ui.write_eventcode = ''
105-
self.csvfile = open(self.temp_filename, 'w', encoding="utf", newline="")
112+
self.csvfile = open(temp_filename, 'w', encoding="utf", newline="")
106113
self.writer = csv.writer(self.csvfile)
107114

108115
ui_file.close()
@@ -150,6 +157,14 @@ def load_exp_params(self):
150157
self.csv_header = self.ui.channels + ["arduino_ts", "event_code"]
151158
self.writer.writerow(self.csv_header)
152159

160+
# External Sync
161+
self.ui.ext_sync_flag = self.ui.params_dict["external_sync"]["enable"]
162+
if self.ui.ext_sync_flag:
163+
self.ui.sync_role = self.ui.params_dict["external_sync"]["role"]
164+
self.ui.sync_ip = self.ui.params_dict["external_sync"]["tcp_ip"]
165+
self.ui.sync_port = self.ui.params_dict["external_sync"]["tcp_port"]
166+
self.ui.sync_obj = External_Sync(self.ui.sync_role, self.ui.sync_ip, self.ui.sync_port)
167+
153168
self.ui.exp_loaded = True
154169
self.ui.pushButton_exp_params.setEnabled(False)
155170
self.ui.label_status.setText("Loaded experiment parameters successfully")
@@ -168,6 +183,7 @@ def load_exp_params(self):
168183

169184
def addData_callbackFunc(self, value):
170185
# print("Add data: " + str(value))
186+
171187
self.myFig.addData(value)
172188

173189
if self.ui.data_record_flag:
@@ -184,13 +200,14 @@ def addData_callbackFunc(self, value):
184200

185201

186202
def stop_record_process(self):
203+
global temp_filename
187204
if not self.csvfile.closed:
188205
self.csvfile.close()
189206
time.sleep(1)
190207
self.save_file_path = os.path.join(self.ui.data_root_dir, self.ui.pid + "_" +
191208
self.ui.curr_exp_name + '_' + self.ui.curr_exp_condition + '_' + self.ui.utc_sec + '.csv')
192-
if os.path.exists(self.temp_filename):
193-
shutil.move(self.temp_filename, self.save_file_path)
209+
if os.path.exists(temp_filename):
210+
shutil.move(temp_filename, self.save_file_path)
194211
self.ui.label_status.setText("Recording stopped and data saved for: Exp - " + self.ui.curr_exp_name + "; Condition - " + self.ui.curr_exp_condition)
195212
else:
196213
self.ui.label_status.setText("Error saving data")
@@ -205,7 +222,7 @@ def stop_record_process(self):
205222
self.myFig.event_toggle = True
206223

207224
# prepare for next recording
208-
self.csvfile = open(self.temp_filename, 'w', encoding="utf", newline="")
225+
self.csvfile = open(temp_filename, 'w', encoding="utf", newline="")
209226
self.writer = csv.writer(self.csvfile)
210227
self.writer.writerow(self.csv_header)
211228

@@ -254,7 +271,7 @@ def connect_serial_port(self):
254271

255272

256273
def start_acquisition(self):
257-
global live_acquisition_flag
274+
global live_acquisition_flag, temp_filename
258275
if not live_acquisition_flag:
259276
live_acquisition_flag = True
260277
if not self.ppgDataLoop_started:
@@ -274,12 +291,12 @@ def start_acquisition(self):
274291
# To reset the graph and clear the values
275292

276293
self.myFig.reset_draw()
277-
if os.path.exists(self.temp_filename):
294+
if os.path.exists(temp_filename):
278295
if not self.csvfile.closed:
279296
self.csvfile.close()
280-
os.remove(self.temp_filename)
297+
os.remove(temp_filename)
281298

282-
self.csvfile = open(self.temp_filename, 'w', encoding="utf", newline="")
299+
self.csvfile = open(temp_filename, 'w', encoding="utf", newline="")
283300
self.writer = csv.writer(self.csvfile)
284301
self.writer.writerow(self.csv_header)
285302

@@ -290,14 +307,29 @@ def start_acquisition(self):
290307
self.ui.listWidget_expConditions.setEnabled(True)
291308

292309

293-
294-
def record_data(self):
295-
if not self.ui.data_record_flag:
296-
if not os.path.exists(self.temp_filename):
297-
self.csvfile = open(self.temp_filename, 'w', encoding="utf", newline="")
298-
self.writer = csv.writer(self.csvfile)
299-
self.writer.writerow(self.csv_header)
310+
def start_record_process(self):
311+
312+
global temp_filename
313+
self.ui.pushButton_record_data.setText("Staring to Record...")
314+
self.ui.pushButton_record_data.setEnabled(False)
315+
316+
if not os.path.exists(temp_filename):
317+
self.csvfile = open(temp_filename, 'w', encoding="utf", newline="")
318+
self.writer = csv.writer(self.csvfile)
319+
self.writer.writerow(self.csv_header)
320+
321+
sync_signal = False
322+
if self.ui.ext_sync_flag:
323+
if self.ui.sync_role == "server":
324+
self.ui.label_status.setText("Server is ready, waiting for external sync to start recording")
325+
sync_signal = self.ui.sync_obj.run_server()
326+
elif self.ui.sync_role == "client":
327+
self.ui.sync_obj.run_client()
328+
sync_signal = True
329+
else:
330+
sync_signal = True
300331

332+
if sync_signal:
301333
self.ui.record_start_time = datetime.now()
302334
self.ui.data_record_flag = True
303335

@@ -306,6 +338,8 @@ def record_data(self):
306338

307339
# self.ui.utc_timestamp_signal = datetime.utcnow()
308340
self.ui.pushButton_record_data.setText("Stop Recording")
341+
self.ui.pushButton_record_data.setEnabled(True)
342+
309343
if self.ui.timed_acquisition:
310344
self.ui.label_status.setText("Timed Recording started for: Exp - " + self.ui.curr_exp_name + "; Condition - " + self.ui.curr_exp_condition + "; Max-Time: " + str(self.ui.max_acquisition_time))
311345
else:
@@ -314,11 +348,18 @@ def record_data(self):
314348
self.ui.lineEdit_Event.setEnabled(True)
315349
self.ui.pushButton_Event.setEnabled(True)
316350
self.ui.event_status = False
351+
317352
try:
318353
self.ui.eventcode = int(self.ui.lineEdit_Event.text())
319354
except:
320355
self.ui.label_status.setText("Incorrect entry for evencode, using eventcode = 0")
321356
self.ui.eventcode = 0
357+
358+
359+
def record_data(self):
360+
if not self.ui.data_record_flag:
361+
start_record_thread = threading.Thread(name='start_record', target=self.start_record_process, daemon=True)
362+
start_record_thread.start()
322363

323364
else:
324365
self.ui.data_record_flag = False
@@ -401,16 +442,11 @@ def reset_draw(self):
401442

402443

403444
def addData(self, value):
404-
global live_acquisition_flag
405-
val_filt = []
406-
for nCh in range(self.nChannels):
407-
val_filt.append(self.filt_objs[str(nCh)].lfilt(value[nCh]))
445+
self.count_frame += 1
408446

409-
if live_acquisition_flag:
410-
self.count_frame += 1
411-
for nCh in range(self.nChannels):
412-
self.plot_signals[nCh] = np.roll(self.plot_signals[nCh], -1)
413-
self.plot_signals[nCh][-1] = val_filt[nCh]
447+
for nCh in range(self.nChannels):
448+
self.plot_signals[nCh] = np.roll(self.plot_signals[nCh], -1)
449+
self.plot_signals[nCh][-1] = self.filt_objs[str(nCh)].lfilt(value[nCh])
414450
return
415451

416452

@@ -510,9 +546,8 @@ def main(app):
510546
ret = app.exec()
511547
del widget
512548

513-
fn = "temp.csv"
514-
if os.path.exists(fn):
515-
os.remove(fn)
549+
if os.path.exists(temp_filename):
550+
os.remove(temp_filename)
516551

517552
# sys.exit(ret)
518553
return

utils/external_sync.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import socket
2+
3+
class External_Sync(object):
4+
def __init__(self, role, ip, port) -> None:
5+
self.role = role
6+
self.ip = ip
7+
self.port = port
8+
self.buffer_size = 1024
9+
# if self.role == "server":
10+
# self.run_server()
11+
# else:
12+
# self.run_client()
13+
14+
def run_server(self):
15+
sync_received = False
16+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
17+
s.bind((self.ip, self.port))
18+
s.listen()
19+
conn, addr = s.accept()
20+
sync_received = True
21+
# with conn:
22+
# print(f"Connected by {addr}")
23+
# while True:
24+
# data = conn.recv(self.buffer_size)
25+
# print(data)
26+
# if data == "1":
27+
# sync_received = True
28+
# # conn.sendall(data)
29+
# break
30+
# elif not data:
31+
# break
32+
return sync_received
33+
34+
35+
def run_client(self):
36+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
37+
s.connect((self.ip, self.port))
38+
s.sendall(b"1")
39+
# data = s.recv(self.buffer_size)
40+
41+
# print(f"Received {data!r}")

0 commit comments

Comments
 (0)