From 7434ed2c88941e7a9b575f3ce77365d7981279b8 Mon Sep 17 00:00:00 2001 From: Joan Clark Nicolas Date: Thu, 6 Jul 2023 12:06:16 +0800 Subject: [PATCH 01/16] Added script example.py --- example.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 example.py diff --git a/example.py b/example.py new file mode 100644 index 0000000..d384f16 --- /dev/null +++ b/example.py @@ -0,0 +1,20 @@ +import serial +import time +ser = serial.Serial('com4') #defines the port that will be used (how do we know that?) +ser.boudrate = 9600 #sets the speed at which data will be transferred to ARDUINO (default?) +ser.flush() # flushes the port buffer +input("Press any key to start\n") #wait +print("Now we are going to generate an odour on channel 1") +print('The odour "duration" of 200ms is controlled by Sniff-0 ') +input("Press any key to generate the odour\n") +ser.write(b'setchannel 1\r') +ser.write(b'openvalvetimed 200\r') +print("Now the clean air channel is going to be opened") +print("Clean air will be delivered for 1000ms") +input("Press any key to open clean air\n") +ser.write(b'setchannel 0\r') +ser.write(b'setvalve 1\r') +time.sleep(1) +ser.write(b'setvalve 0\r') +print("End") +ser.close() \ No newline at end of file From 27706dc0d35f1c99d5a1bb95391c542d89ea3c91 Mon Sep 17 00:00:00 2001 From: Joan Clark Nicolas Date: Thu, 6 Jul 2023 12:06:29 +0800 Subject: [PATCH 02/16] Added main script --- olfactometer_main.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 olfactometer_main.py diff --git a/olfactometer_main.py b/olfactometer_main.py new file mode 100644 index 0000000..14e922e --- /dev/null +++ b/olfactometer_main.py @@ -0,0 +1,14 @@ +import olfactometer +import argparse + +def parse(): + parser = argparse.ArgumentParser() + parser.add_argument('-e',"--experiment",help="name of the experiment to run") + + args = parser.parse_args() + return args + +if __name__=="__main__": + args=parse() + expt=getattr(olfactometer,args.experiment) # gets the module in the olfactometer folder + expt() \ No newline at end of file From aab5302b0afd4ab2f0a8d6931ccec3b50adc5824 Mon Sep 17 00:00:00 2001 From: Joan Clark Nicolas Date: Thu, 6 Jul 2023 12:06:57 +0800 Subject: [PATCH 03/16] Added module olfactometer and it loads all submodules automatically --- olfactometer/__init__.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 olfactometer/__init__.py diff --git a/olfactometer/__init__.py b/olfactometer/__init__.py new file mode 100644 index 0000000..fff19cf --- /dev/null +++ b/olfactometer/__init__.py @@ -0,0 +1,24 @@ +import serial +import time + +""" +The following code will import all files in the folder +There has to be a simpler way to do it, but this works +""" +from inspect import isclass +from pkgutil import iter_modules +from pathlib import Path +from importlib import import_module + +# iterate through the modules in the current package +package_dir = Path(__file__).resolve().parent +for (_, module_name, _) in iter_modules([package_dir]): + + # import the module and iterate through its attributes + module = import_module(f"{__name__}.{module_name}") + for attribute_name in dir(module): + attribute = getattr(module, attribute_name) + + if isclass(attribute): + # Add the class to this package's variables + globals()[attribute_name] = attribute \ No newline at end of file From 3e1907bba00cccb9cda93d939e47bfcd4c5f8142 Mon Sep 17 00:00:00 2001 From: Joan Clark Nicolas Date: Thu, 6 Jul 2023 12:07:05 +0800 Subject: [PATCH 04/16] Added function clean --- olfactometer/clean.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 olfactometer/clean.py diff --git a/olfactometer/clean.py b/olfactometer/clean.py new file mode 100644 index 0000000..5b36b90 --- /dev/null +++ b/olfactometer/clean.py @@ -0,0 +1,22 @@ +import serial +import time + +""" +Flush the olfactometer tubes with clean air for the specified +amount of time in seconds +""" +def clean(): + ser = serial.Serial('com4') #defines the port that will be used (how do we know that?) + ser.boudrate = 9600 #sets the speed at which data will be transferred to ARDUINO (default?) + ser.flush() + ser.write(b'setchannel 0\r') #set to channel 0 (clean air) + ser.write(b'setvalve 1\r') # set valve to 1 (open) + time.sleep(1) # wait + ser.write(b'setvalve 0\r') # set valve to 0 (closed) + ser.close() + +if __name__=="__main__": + print("Executing script clean.py on its own.") +else: + print("Importing script clean.py as "+__name__) +clean() From 154e8bdedbb2cfd45101ef6c786b4c2ecd60263f Mon Sep 17 00:00:00 2001 From: Joan Clark Nicolas Date: Thu, 6 Jul 2023 12:07:18 +0800 Subject: [PATCH 05/16] Added readme --- README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e07a135..ba2e82c 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,10 @@ # olfactometer -Set of python scripts used to automate olfacotmeter tasks at SWRI +Set of python scripts used to automate olfacotmeter tasks at SWRI. +All experiments will start and end by emitting clean air for 1 second, so that no cross-contamination may occur between experiments + +USAGE + +DEPENDENCIES + - pyserial: helps python interact with serial ports. can be installed with "pip install pyserial" on the computer that will use the olfactometer. + - time: helps add pauses between opening valves when needed. As far as I am aware, this module comes with any default python installation. +SCRIPTS \ No newline at end of file From 695d72258628846075ca458c6b36366bdafec827 Mon Sep 17 00:00:00 2001 From: Joan Clark Nicolas Date: Wed, 19 Jul 2023 09:27:25 +0700 Subject: [PATCH 06/16] Added port number as a parameter. Fixed clean.py Added threshold.py --- olfactometer/clean.py | 17 ++++++++++--- olfactometer/threshold.py | 52 +++++++++++++++++++++++++++++++++++++++ olfactometer_main.py | 3 ++- 3 files changed, 67 insertions(+), 5 deletions(-) create mode 100644 olfactometer/threshold.py diff --git a/olfactometer/clean.py b/olfactometer/clean.py index 5b36b90..318b9b3 100644 --- a/olfactometer/clean.py +++ b/olfactometer/clean.py @@ -1,22 +1,31 @@ import serial import time +import argparse """ Flush the olfactometer tubes with clean air for the specified amount of time in seconds """ -def clean(): - ser = serial.Serial('com4') #defines the port that will be used (how do we know that?) +def parse(): + parser = argparse.ArgumentParser() + parser.add_argument('-p',"--port",help="Name of port where the instrument is connected (default=com3)",default="com3") + + args = parser.parse_args() + return args +def clean(port): + ser = serial.Serial(port) #defines the port that will be used (how do we know that?) ser.boudrate = 9600 #sets the speed at which data will be transferred to ARDUINO (default?) ser.flush() ser.write(b'setchannel 0\r') #set to channel 0 (clean air) ser.write(b'setvalve 1\r') # set valve to 1 (open) - time.sleep(1) # wait + time.sleep(10) # wait ser.write(b'setvalve 0\r') # set valve to 0 (closed) ser.close() if __name__=="__main__": print("Executing script clean.py on its own.") + args=parse() + port=args.port else: print("Importing script clean.py as "+__name__) -clean() +clean(port) diff --git a/olfactometer/threshold.py b/olfactometer/threshold.py new file mode 100644 index 0000000..8f997d0 --- /dev/null +++ b/olfactometer/threshold.py @@ -0,0 +1,52 @@ +import serial +import time +import argparse + +""" +Open channel 0 (connstant flow), then open and close channels 1 to 13 (odorants) in succession. +Open channel 0 + Open channel 1 for 10 sec, then close + Wait 10 sec + Open channel 2 for 10 sec, then close + Wait 10 sec + And so o +Close channel 0 +""" +def parse(): + parser = argparse.ArgumentParser() + parser.add_argument('-p',"--port",help="Name of port where the instrument is connected (default=com3)",default="com3") + + args = parser.parse_args() + return args +def threshold(port): + ser = serial.Serial(port) #defines the port that will be used (how do we know that?) + ser.boudrate = 9600 #sets the speed at which data will be transferred to ARDUINO (default?) + ser.flush() + + # Open constant flow + ser.write(b'setchannel 0\r') #set to channel 0 (constant flow) + ser.write(b'setflow 0:10;\r') # set flow of channel 0 to 10 SPLM (too much?) + ser.write(b'setvalve 1\r') # set valve to 1 (open) + flow=0.1 # odorant channel flows will be 0.1 SPLM (needs to be negligible in comparison to constant flow) + for i in range(1,13): # i is the channel number + channel_str="setchannel "+str(i)+"\r" + flow_str="setflow "+str(i)+":"+str(flow)+";\r" + ser.write(bytes(channel_str)) # set channel + ser.write(bytes(flow_str)) # set flow + ser.write(b'setvalve 1\r') # open + time.sleep(10) # wait + ser.write(b'setvalve 0\r') # close + time.sleep(10) # wait + + #close constant flow + ser.write(b'setchannel 0\r') #set to channel 0 (constant flow) + ser.write(b'setvalve 0\r') # set valve to 1 (open) + ser.close() + +if __name__=="__main__": + print("Executing script threshold.py on its own.") + args=parse() + port=args.port +else: + print("Importing script threshold.py as "+__name__) +threshold(port) diff --git a/olfactometer_main.py b/olfactometer_main.py index 14e922e..6bd4029 100644 --- a/olfactometer_main.py +++ b/olfactometer_main.py @@ -4,6 +4,7 @@ def parse(): parser = argparse.ArgumentParser() parser.add_argument('-e',"--experiment",help="name of the experiment to run") + parser.add_argument('-p',"--port",help="Name of port where the instrument is connected (default=com3)",default="com3") args = parser.parse_args() return args @@ -11,4 +12,4 @@ def parse(): if __name__=="__main__": args=parse() expt=getattr(olfactometer,args.experiment) # gets the module in the olfactometer folder - expt() \ No newline at end of file + expt(args.port) \ No newline at end of file From 7866e0c1f058f401b0249ab31e092e16ac77c0ef Mon Sep 17 00:00:00 2001 From: Joan Clark Nicolas Date: Wed, 19 Jul 2023 09:30:37 +0700 Subject: [PATCH 07/16] threshold.py is now called channeltest.py --- olfactometer/{threshold.py => channeltest.py} | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) rename olfactometer/{threshold.py => channeltest.py} (91%) diff --git a/olfactometer/threshold.py b/olfactometer/channeltest.py similarity index 91% rename from olfactometer/threshold.py rename to olfactometer/channeltest.py index 8f997d0..c3b9b0d 100644 --- a/olfactometer/threshold.py +++ b/olfactometer/channeltest.py @@ -18,7 +18,7 @@ def parse(): args = parser.parse_args() return args -def threshold(port): +def channeltest(port): ser = serial.Serial(port) #defines the port that will be used (how do we know that?) ser.boudrate = 9600 #sets the speed at which data will be transferred to ARDUINO (default?) ser.flush() @@ -44,9 +44,9 @@ def threshold(port): ser.close() if __name__=="__main__": - print("Executing script threshold.py on its own.") + print("Executing script channeltest.py on its own.") args=parse() port=args.port else: - print("Importing script threshold.py as "+__name__) -threshold(port) + print("Importing script channeltest.py as "+__name__) +channeltest(port) From a9fffdd281e485bb4e694d13199c886e7d797236 Mon Sep 17 00:00:00 2001 From: carajillu Date: Sat, 22 Jul 2023 18:26:51 +0700 Subject: [PATCH 08/16] Added olfactometer_yaml, which is better for expts --- olfactometer_yaml.py | 106 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 olfactometer_yaml.py diff --git a/olfactometer_yaml.py b/olfactometer_yaml.py new file mode 100644 index 0000000..cd6eda0 --- /dev/null +++ b/olfactometer_yaml.py @@ -0,0 +1,106 @@ +import serial +import argparse +import yaml +import sys + +def parse(): + parser = argparse.ArgumentParser() + parser.add_argument('-i',"--input",help="Name of the yaml file with the experiment",default=None) + args = parser.parse_args() + return args + +def set_parameters(yml): + print(yml) + if list(yml.keys())[0]!="parameters": # In python 3, dict_keys object is not subscriptable + print ("First key in input file is not parameters. Exiting.") + sys.exit() + else: + port=yml["parameters"]["port"] + total_flow=yml["parameters"]["total_flow"] + constant_flow_id=yml["parameters"]["constant_flow_channel_id"] + clean_air_id=yml["parameters"]["clean_air_channel_id"] + print("Olfactometer will be runing from port ", port) + print("Total flow set to ", total_flow, " SPLM") + print("Constant flow will come from channel ", constant_flow_id) + print("Clean air will come from channel ", clean_air_id) + return port, total_flow, constant_flow_id, clean_air_id + + +def check_expts(yml): + for key in list(yml.keys())[1:]: # first key is ALWAYS the parameters + print("Checking step: ",key) + if len(yml[key]["channel_id"])==len(yml[key]["flow"]): + print("... Step ", key, " OK") + else: + print("... Step ", key, " malformatted. Check that channel_id, flow and time have the same number of values") + sys.exit() + return + +def run_expt(yml,ser, total_flow, constant_flow_id, clean_air_id): + for key in list(yml.keys())[1:]: # first key is ALWAYS the parameters + flow_sum=0 + flow_str="setflow " + time=yml[key]["time"] + constant_flow_rate=total_flow + for i in range(0,len(yml[key]["channel_id"])): + channel_id=yml[key]["channel_id"][i] + flow_i=yml[key]["flow"][i] + print("channel ", channel_id, " will be open at ", flow_i, " SPLM for ", time, " seconds") + + #setting the flow of each channel + flow_str=flow_str+str(channel_id)+":"+str(flow_i)+";" + constant_flow_rate=constant_flow_rate-flow_i + + if flow_sum>total_flow: + print("Total flow will be set to ", flow_sum, "SPLM which is larger than indicated on the parameters.") + print("constant flow rate will be set to 0 SPLM") + print("this happens when the sum of the odorant flows is larger than the specified total flow") + z=input("press enter to continue or ctrl+c to kill the execution") + + #calculate the new rate of the constant flow and calibrate the channel flows + constant_flow_rate=total_flow-flow_sum + if constant_flow_rate>0: + flow_str=flow_str+str(constant_flow_id)+":"+str(constant_flow_rate)+";\r" + #ser.write(bytes(flow_str)) + + # Open the constant flow (not timed) + if constant_flow_rate>0: + cmd_str="setchannel "+str(constant_flow_id)+"\r" + #ser.write(bytes(cmd_str)) + cmd_str="setvalve 1\r" + #ser.write(bytes(cmd_str)) + + # Open the odorant channels (timed) + for i in range(0,len(yml[key]["channel_id"])): + channel_id=yml[key]["channel_id"][i] + flow_i=yml[key]["flow"][i] + cmd_str="setchannel "+str(channel_id)+"\r" + #ser.write(bytes(cmd_str)) + cmd_str="openvalvetimed "+str(time)+"\r" + #ser.write(bytes(cmd_str)) + + #close the constant flow + cmd_str="setchannel "+str(constant_flow_id)+"\r" + #ser.write(bytes(cmd_str)) + cmd_str="setvalve 1\r" + #ser.write(bytes(cmd_str)) + return + +if __name__=="__main__": + args=parse() + with open(args.input, 'r') as file: + try: + yml = yaml.safe_load(file) + except Exception as error: + print("The input file has formatting errors.") + print(error) + sys.exit() + else: + port, total_flow, constant_flow_id, clean_air_id=set_parameters(yml) + #ser = serial.Serial(port) + ser=None + check_expts(yml) + run_expt(yml,ser, total_flow, constant_flow_id, clean_air_id) + + + \ No newline at end of file From 00a2691c66304bf5ae416a93d69df631c8051d05 Mon Sep 17 00:00:00 2001 From: carajillu Date: Sat, 22 Jul 2023 18:31:36 +0700 Subject: [PATCH 09/16] minor bug fixed --- olfactometer_yaml.py | 1 + 1 file changed, 1 insertion(+) diff --git a/olfactometer_yaml.py b/olfactometer_yaml.py index cd6eda0..50d8564 100644 --- a/olfactometer_yaml.py +++ b/olfactometer_yaml.py @@ -45,6 +45,7 @@ def run_expt(yml,ser, total_flow, constant_flow_id, clean_air_id): for i in range(0,len(yml[key]["channel_id"])): channel_id=yml[key]["channel_id"][i] flow_i=yml[key]["flow"][i] + flow_sum=flow_sum+flow_i print("channel ", channel_id, " will be open at ", flow_i, " SPLM for ", time, " seconds") #setting the flow of each channel From 8553c7b80e5d564665a3ea5b284a6fae9c987039 Mon Sep 17 00:00:00 2001 From: carajillu Date: Tue, 25 Jul 2023 15:21:11 +0700 Subject: [PATCH 10/16] Added the funtion so that I do't have to comment out the ser.write() lines Also added 2 config files --- experiments_yaml/clean.yml | 12 ++++ experiments_yaml/proficiency.yml | 18 ++++++ olfactometer_yaml.py | 108 +++++++++++++++++-------------- 3 files changed, 88 insertions(+), 50 deletions(-) create mode 100644 experiments_yaml/clean.yml create mode 100644 experiments_yaml/proficiency.yml diff --git a/experiments_yaml/clean.yml b/experiments_yaml/clean.yml new file mode 100644 index 0000000..dbb99f0 --- /dev/null +++ b/experiments_yaml/clean.yml @@ -0,0 +1,12 @@ +parameters: + port: "com3" + constant_flow_channel_id: 0 + constant_flow_rate: 2 + clean_air_channel_id: 13 + + +clean: + channel_id: [13] + flow: [2] + time: 10 + pause: 10 diff --git a/experiments_yaml/proficiency.yml b/experiments_yaml/proficiency.yml new file mode 100644 index 0000000..9165804 --- /dev/null +++ b/experiments_yaml/proficiency.yml @@ -0,0 +1,18 @@ +parameters: + port: "com3" + constant_flow_channel_id: 0 + constant_flow_rate: 2 + clean_air_channel_id: 13 + + +clean: + channel_id: [13] + flow: [2] + time: 10 + pause: 10 + +proficiency: + channel_id: [1,13,2,13,3,13,4,13,5,13,6,13,7,13,8,13,9,13,10,13,11,13,12,13] + flow: [2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2] + time: 10 + pause: 10 diff --git a/olfactometer_yaml.py b/olfactometer_yaml.py index 50d8564..f9bfe57 100644 --- a/olfactometer_yaml.py +++ b/olfactometer_yaml.py @@ -2,6 +2,7 @@ import argparse import yaml import sys +import time def parse(): parser = argparse.ArgumentParser() @@ -10,22 +11,20 @@ def parse(): return args def set_parameters(yml): - print(yml) if list(yml.keys())[0]!="parameters": # In python 3, dict_keys object is not subscriptable print ("First key in input file is not parameters. Exiting.") sys.exit() else: port=yml["parameters"]["port"] - total_flow=yml["parameters"]["total_flow"] + constant_flow_rate=yml["parameters"]["constant_flow_rate"] constant_flow_id=yml["parameters"]["constant_flow_channel_id"] clean_air_id=yml["parameters"]["clean_air_channel_id"] print("Olfactometer will be runing from port ", port) - print("Total flow set to ", total_flow, " SPLM") print("Constant flow will come from channel ", constant_flow_id) + print("Constant flow will be set at ", constant_flow_rate," SPLM") print("Clean air will come from channel ", clean_air_id) - return port, total_flow, constant_flow_id, clean_air_id - - + return port, constant_flow_rate, constant_flow_id, clean_air_id + def check_expts(yml): for key in list(yml.keys())[1:]: # first key is ALWAYS the parameters print("Checking step: ",key) @@ -36,55 +35,58 @@ def check_expts(yml): sys.exit() return -def run_expt(yml,ser, total_flow, constant_flow_id, clean_air_id): +def run_expt(yml,ser, constant_flow_rate, constant_flow_id, clean_air_id): + # 1) Open the constant flow channel + cmd_str="setflow "+str(constant_flow_id)+":"+str(constant_flow_rate)+";" + ser_exec(ser,cmd_str) + + cmd_str="setchannel "+str(constant_flow_id) + ser_exec(ser,cmd_str) + + cmd_str="setvalve 1" + ser_exec(ser,cmd_str) + + # 2) Run experiments for key in list(yml.keys())[1:]: # first key is ALWAYS the parameters - flow_sum=0 - flow_str="setflow " - time=yml[key]["time"] - constant_flow_rate=total_flow + + timeopenvalve=yml[key]["time"] #This is the time this experiment will run for + + # 2.1) Calibrate flows of odorant channels (incl clean air) + cmd_str="setflow " for i in range(0,len(yml[key]["channel_id"])): channel_id=yml[key]["channel_id"][i] flow_i=yml[key]["flow"][i] - flow_sum=flow_sum+flow_i - print("channel ", channel_id, " will be open at ", flow_i, " SPLM for ", time, " seconds") - + print("channel ", channel_id, " will be open at ", flow_i, " SPLM for ", timeopenvalve, " seconds") #setting the flow of each channel - flow_str=flow_str+str(channel_id)+":"+str(flow_i)+";" - constant_flow_rate=constant_flow_rate-flow_i - - if flow_sum>total_flow: - print("Total flow will be set to ", flow_sum, "SPLM which is larger than indicated on the parameters.") - print("constant flow rate will be set to 0 SPLM") - print("this happens when the sum of the odorant flows is larger than the specified total flow") - z=input("press enter to continue or ctrl+c to kill the execution") - - #calculate the new rate of the constant flow and calibrate the channel flows - constant_flow_rate=total_flow-flow_sum - if constant_flow_rate>0: - flow_str=flow_str+str(constant_flow_id)+":"+str(constant_flow_rate)+";\r" - #ser.write(bytes(flow_str)) - - # Open the constant flow (not timed) - if constant_flow_rate>0: - cmd_str="setchannel "+str(constant_flow_id)+"\r" - #ser.write(bytes(cmd_str)) - cmd_str="setvalve 1\r" - #ser.write(bytes(cmd_str)) + cmd_str=cmd_str+str(channel_id)+":"+str(flow_i)+";" + ser_exec(ser,cmd_str) - # Open the odorant channels (timed) + # 2.2) Open the odorant channels (timed) + time_ms=timeopenvalve*1000 for i in range(0,len(yml[key]["channel_id"])): channel_id=yml[key]["channel_id"][i] - flow_i=yml[key]["flow"][i] - cmd_str="setchannel "+str(channel_id)+"\r" - #ser.write(bytes(cmd_str)) - cmd_str="openvalvetimed "+str(time)+"\r" - #ser.write(bytes(cmd_str)) + + cmd_str="setchannel "+str(channel_id) + ser_exec(ser,cmd_str) + + cmd_str="openvalvetimed "+str(time_ms) + ser_exec(ser,cmd_str) + + pause=int(yml[key]["pause"]) + time.sleep(pause) - #close the constant flow - cmd_str="setchannel "+str(constant_flow_id)+"\r" - #ser.write(bytes(cmd_str)) - cmd_str="setvalve 1\r" - #ser.write(bytes(cmd_str)) + # 3) Close the constant flow + cmd_str="setchannel "+str(constant_flow_id) + ser_exec(ser,cmd_str) + + cmd_str="setvalve 0" + ser_exec(ser,cmd_str) + return + +def ser_exec(ser,cmd_str): + print("WRITING TO SERIAL: ", cmd_str) + if ser is not None: + ser.write(bytes(cmd_str+"\r")) return if __name__=="__main__": @@ -97,11 +99,17 @@ def run_expt(yml,ser, total_flow, constant_flow_id, clean_air_id): print(error) sys.exit() else: - port, total_flow, constant_flow_id, clean_air_id=set_parameters(yml) - #ser = serial.Serial(port) - ser=None + port, constant_flow_rate, constant_flow_id, clean_air_id=set_parameters(yml) + try: + ser = serial.Serial(port) + except: + print("Serial Port ", port, " cannot be reached.") + print("Running in emulator mode.") + print("Press enter to continue or CTRL+C to break execution.") + ser=None + z=input() check_expts(yml) - run_expt(yml,ser, total_flow, constant_flow_id, clean_air_id) + run_expt(yml,ser, constant_flow_rate, constant_flow_id, clean_air_id) \ No newline at end of file From cee83da85a8fed4e97200da74b51ef1d8f594321 Mon Sep 17 00:00:00 2001 From: carajillu Date: Tue, 1 Aug 2023 18:59:00 +0700 Subject: [PATCH 11/16] Now using encode() class to encode strings for the instruments --- olfactometer_yaml.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/olfactometer_yaml.py b/olfactometer_yaml.py index f9bfe57..340ae9f 100644 --- a/olfactometer_yaml.py +++ b/olfactometer_yaml.py @@ -86,7 +86,8 @@ def run_expt(yml,ser, constant_flow_rate, constant_flow_id, clean_air_id): def ser_exec(ser,cmd_str): print("WRITING TO SERIAL: ", cmd_str) if ser is not None: - ser.write(bytes(cmd_str+"\r")) + cmd_bytes=(cmd_str+"\r").encode() + ser.write(cmd_bytes) return if __name__=="__main__": From 145212f8b4d39c3d0e2f99d07d7b9f219870db69 Mon Sep 17 00:00:00 2001 From: carajillu Date: Sat, 5 Aug 2023 16:19:10 +0800 Subject: [PATCH 12/16] Deleted old module --- olfactometer/__init__.py | 24 ----------------- olfactometer/channeltest.py | 52 ------------------------------------- olfactometer/clean.py | 31 ---------------------- olfactometer_main.py | 15 ----------- 4 files changed, 122 deletions(-) delete mode 100644 olfactometer/__init__.py delete mode 100644 olfactometer/channeltest.py delete mode 100644 olfactometer/clean.py delete mode 100644 olfactometer_main.py diff --git a/olfactometer/__init__.py b/olfactometer/__init__.py deleted file mode 100644 index fff19cf..0000000 --- a/olfactometer/__init__.py +++ /dev/null @@ -1,24 +0,0 @@ -import serial -import time - -""" -The following code will import all files in the folder -There has to be a simpler way to do it, but this works -""" -from inspect import isclass -from pkgutil import iter_modules -from pathlib import Path -from importlib import import_module - -# iterate through the modules in the current package -package_dir = Path(__file__).resolve().parent -for (_, module_name, _) in iter_modules([package_dir]): - - # import the module and iterate through its attributes - module = import_module(f"{__name__}.{module_name}") - for attribute_name in dir(module): - attribute = getattr(module, attribute_name) - - if isclass(attribute): - # Add the class to this package's variables - globals()[attribute_name] = attribute \ No newline at end of file diff --git a/olfactometer/channeltest.py b/olfactometer/channeltest.py deleted file mode 100644 index c3b9b0d..0000000 --- a/olfactometer/channeltest.py +++ /dev/null @@ -1,52 +0,0 @@ -import serial -import time -import argparse - -""" -Open channel 0 (connstant flow), then open and close channels 1 to 13 (odorants) in succession. -Open channel 0 - Open channel 1 for 10 sec, then close - Wait 10 sec - Open channel 2 for 10 sec, then close - Wait 10 sec - And so o -Close channel 0 -""" -def parse(): - parser = argparse.ArgumentParser() - parser.add_argument('-p',"--port",help="Name of port where the instrument is connected (default=com3)",default="com3") - - args = parser.parse_args() - return args -def channeltest(port): - ser = serial.Serial(port) #defines the port that will be used (how do we know that?) - ser.boudrate = 9600 #sets the speed at which data will be transferred to ARDUINO (default?) - ser.flush() - - # Open constant flow - ser.write(b'setchannel 0\r') #set to channel 0 (constant flow) - ser.write(b'setflow 0:10;\r') # set flow of channel 0 to 10 SPLM (too much?) - ser.write(b'setvalve 1\r') # set valve to 1 (open) - flow=0.1 # odorant channel flows will be 0.1 SPLM (needs to be negligible in comparison to constant flow) - for i in range(1,13): # i is the channel number - channel_str="setchannel "+str(i)+"\r" - flow_str="setflow "+str(i)+":"+str(flow)+";\r" - ser.write(bytes(channel_str)) # set channel - ser.write(bytes(flow_str)) # set flow - ser.write(b'setvalve 1\r') # open - time.sleep(10) # wait - ser.write(b'setvalve 0\r') # close - time.sleep(10) # wait - - #close constant flow - ser.write(b'setchannel 0\r') #set to channel 0 (constant flow) - ser.write(b'setvalve 0\r') # set valve to 1 (open) - ser.close() - -if __name__=="__main__": - print("Executing script channeltest.py on its own.") - args=parse() - port=args.port -else: - print("Importing script channeltest.py as "+__name__) -channeltest(port) diff --git a/olfactometer/clean.py b/olfactometer/clean.py deleted file mode 100644 index 318b9b3..0000000 --- a/olfactometer/clean.py +++ /dev/null @@ -1,31 +0,0 @@ -import serial -import time -import argparse - -""" -Flush the olfactometer tubes with clean air for the specified -amount of time in seconds -""" -def parse(): - parser = argparse.ArgumentParser() - parser.add_argument('-p',"--port",help="Name of port where the instrument is connected (default=com3)",default="com3") - - args = parser.parse_args() - return args -def clean(port): - ser = serial.Serial(port) #defines the port that will be used (how do we know that?) - ser.boudrate = 9600 #sets the speed at which data will be transferred to ARDUINO (default?) - ser.flush() - ser.write(b'setchannel 0\r') #set to channel 0 (clean air) - ser.write(b'setvalve 1\r') # set valve to 1 (open) - time.sleep(10) # wait - ser.write(b'setvalve 0\r') # set valve to 0 (closed) - ser.close() - -if __name__=="__main__": - print("Executing script clean.py on its own.") - args=parse() - port=args.port -else: - print("Importing script clean.py as "+__name__) -clean(port) diff --git a/olfactometer_main.py b/olfactometer_main.py deleted file mode 100644 index 6bd4029..0000000 --- a/olfactometer_main.py +++ /dev/null @@ -1,15 +0,0 @@ -import olfactometer -import argparse - -def parse(): - parser = argparse.ArgumentParser() - parser.add_argument('-e',"--experiment",help="name of the experiment to run") - parser.add_argument('-p',"--port",help="Name of port where the instrument is connected (default=com3)",default="com3") - - args = parser.parse_args() - return args - -if __name__=="__main__": - args=parse() - expt=getattr(olfactometer,args.experiment) # gets the module in the olfactometer folder - expt(args.port) \ No newline at end of file From 2e32c7f277b41a5fd064f95c52f40766858b0c12 Mon Sep 17 00:00:00 2001 From: carajillu Date: Fri, 11 Aug 2023 14:02:42 +0800 Subject: [PATCH 13/16] Added a new experiment template Rejigged the whole thing so that calibration can be done externally if the user so wishes. --- experiments_yaml/expt1.yml | 23 ++++++++ olfactometer_yaml.py | 116 ++++++++++++++++++++++++++----------- 2 files changed, 104 insertions(+), 35 deletions(-) create mode 100644 experiments_yaml/expt1.yml diff --git a/experiments_yaml/expt1.yml b/experiments_yaml/expt1.yml new file mode 100644 index 0000000..5c005ff --- /dev/null +++ b/experiments_yaml/expt1.yml @@ -0,0 +1,23 @@ +parameters: + port: "com3" + constant_flow_channel_id: 0 + constant_flow_rate: 2 + calibration: yes + +clean: + seconds: 20 + channels: + 13: 1 + +test1: + seconds: 20 + channels: + 1: 1 + 2: 1 + 3: 1 + + + + + + diff --git a/olfactometer_yaml.py b/olfactometer_yaml.py index 340ae9f..89d609d 100644 --- a/olfactometer_yaml.py +++ b/olfactometer_yaml.py @@ -18,28 +18,61 @@ def set_parameters(yml): port=yml["parameters"]["port"] constant_flow_rate=yml["parameters"]["constant_flow_rate"] constant_flow_id=yml["parameters"]["constant_flow_channel_id"] - clean_air_id=yml["parameters"]["clean_air_channel_id"] + calibration=yml["parameters"]["calibration"] print("Olfactometer will be runing from port ", port) print("Constant flow will come from channel ", constant_flow_id) print("Constant flow will be set at ", constant_flow_rate," SPLM") - print("Clean air will come from channel ", clean_air_id) - return port, constant_flow_rate, constant_flow_id, clean_air_id + if calibration==True: + print("Channels will be calibrated before starting each experiment.") + else: + print("Calibration will NOT be done for any channels. Make sure it is already done.") + z=input("Press enter to continue or ctrl+C to exit.") + return port, constant_flow_rate, constant_flow_id, calibration -def check_expts(yml): +def check_expts(yml): + if yml["parameters"]["calibration"]==False: + print("Calibration has been done externally. Displayed flow rate values may not be true") for key in list(yml.keys())[1:]: # first key is ALWAYS the parameters - print("Checking step: ",key) - if len(yml[key]["channel_id"])==len(yml[key]["flow"]): - print("... Step ", key, " OK") - else: - print("... Step ", key, " malformatted. Check that channel_id, flow and time have the same number of values") - sys.exit() + print("Checking step:",key, "...") + print("Run time:",yml[key]["seconds"],"seconds") + for channel in list(yml[key]["channels"].keys()): + print("Channel",channel,"will run at",yml[key]["channels"][channel],"SPLM") return -def run_expt(yml,ser, constant_flow_rate, constant_flow_id, clean_air_id): - # 1) Open the constant flow channel - cmd_str="setflow "+str(constant_flow_id)+":"+str(constant_flow_rate)+";" - ser_exec(ser,cmd_str) +def run_calibration(ser,channel,flow): + if ser is None: + print("Cannot verify calibration in emulator mode. Assuming it was successful.") + return True + + cmd_str="setverbose 1" + ser_exec(cmd) + cmd_str="setflow "+str(channel_id)+":"+str(flow_i)+";" + ser.exec(cmd) + + readback=ser_listen(ser) + if readback is None: + return False + if ("Result" in readback): + print (readback) + readback=ser_listen(ser) + if readback is None: + return False + if ("*OK" in readback): + print('Calibration completed successfully') + return True + +def run_expt(yml,ser, constant_flow_rate, constant_flow_id, calibration): + # 0) Calibrate the constant flow channel if required + if calibration is True: + print("calibrating constant flow") + outcome=run_calibration(ser,constant_flow_id,constant_flow_rate) + print(outcome) + if outcome is False: + print("constant flow calibration failed. Exiting.") + return + + # 1) Open constant flow cmd_str="setchannel "+str(constant_flow_id) ser_exec(ser,cmd_str) @@ -48,32 +81,32 @@ def run_expt(yml,ser, constant_flow_rate, constant_flow_id, clean_air_id): # 2) Run experiments for key in list(yml.keys())[1:]: # first key is ALWAYS the parameters - - timeopenvalve=yml[key]["time"] #This is the time this experiment will run for + z=input("Press Enter to start: "+key) + timeopenvalve=yml[key]["seconds"] #This is the time this experiment will run for - # 2.1) Calibrate flows of odorant channels (incl clean air) - cmd_str="setflow " - for i in range(0,len(yml[key]["channel_id"])): - channel_id=yml[key]["channel_id"][i] - flow_i=yml[key]["flow"][i] - print("channel ", channel_id, " will be open at ", flow_i, " SPLM for ", timeopenvalve, " seconds") - #setting the flow of each channel - cmd_str=cmd_str+str(channel_id)+":"+str(flow_i)+";" - ser_exec(ser,cmd_str) + # 2.1) Calibrate flows of odorant channels if required + if calibration is True: + print("calibrating odorant channels") + for channel in list(yml[key]["channels"].keys()): + flow=yml[key]["channels"][channel] + outcome=run_calibration(ser,channel,flow) + print(channel,flow,outcome) + if outcome is False: + print("Calibration of channel",channel,"failed. Exiting.") + return # 2.2) Open the odorant channels (timed) time_ms=timeopenvalve*1000 - for i in range(0,len(yml[key]["channel_id"])): - channel_id=yml[key]["channel_id"][i] - + for channel_id in list(yml[key]["channels"].keys()): cmd_str="setchannel "+str(channel_id) ser_exec(ser,cmd_str) cmd_str="openvalvetimed "+str(time_ms) ser_exec(ser,cmd_str) - - pause=int(yml[key]["pause"]) - time.sleep(pause) + + # 2.3) Once all commands are submitted, wait for the execution time + 10 seconds to catch up. + time.sleep(timeopenvalve+10) + # 3) Close the constant flow cmd_str="setchannel "+str(constant_flow_id) @@ -90,27 +123,40 @@ def ser_exec(ser,cmd_str): ser.write(cmd_bytes) return +def ser_listen(ser): + readback=None + waiting_time=0 + readback = ser.readline().decode('utf-8') + while(readback is None): + readback = ser.readline().decode('utf-8') + time.sleep(1) + waiting_time+=1 + if waiting_time>60: + print("instrument is taking too long to reply. Aborting.") + break + return readback + if __name__=="__main__": args=parse() with open(args.input, 'r') as file: try: yml = yaml.safe_load(file) + print(yml) except Exception as error: print("The input file has formatting errors.") print(error) sys.exit() else: - port, constant_flow_rate, constant_flow_id, clean_air_id=set_parameters(yml) + port, constant_flow_rate, constant_flow_id, calibration=set_parameters(yml) try: ser = serial.Serial(port) except: print("Serial Port ", port, " cannot be reached.") print("Running in emulator mode.") - print("Press enter to continue or CTRL+C to break execution.") ser=None - z=input() + z=input("Press enter to continue or CTRL+C to break execution.") check_expts(yml) - run_expt(yml,ser, constant_flow_rate, constant_flow_id, clean_air_id) + run_expt(yml,ser, constant_flow_rate, constant_flow_id, calibration) \ No newline at end of file From bca3cad49904b54c11612827ee3f860a2cadc334 Mon Sep 17 00:00:00 2001 From: carajillu Date: Thu, 17 Aug 2023 20:49:51 +0800 Subject: [PATCH 14/16] updated expt sample files --- experiments_yaml/clean.yml | 10 ++++------ experiments_yaml/expt1.yml | 2 +- experiments_yaml/proficiency.yml | 18 ------------------ 3 files changed, 5 insertions(+), 25 deletions(-) delete mode 100644 experiments_yaml/proficiency.yml diff --git a/experiments_yaml/clean.yml b/experiments_yaml/clean.yml index dbb99f0..74cc530 100644 --- a/experiments_yaml/clean.yml +++ b/experiments_yaml/clean.yml @@ -2,11 +2,9 @@ parameters: port: "com3" constant_flow_channel_id: 0 constant_flow_rate: 2 - clean_air_channel_id: 13 - + calibration: no clean: - channel_id: [13] - flow: [2] - time: 10 - pause: 10 + seconds: 20 + channels: + 13: 1 \ No newline at end of file diff --git a/experiments_yaml/expt1.yml b/experiments_yaml/expt1.yml index 5c005ff..465b10e 100644 --- a/experiments_yaml/expt1.yml +++ b/experiments_yaml/expt1.yml @@ -2,7 +2,7 @@ parameters: port: "com3" constant_flow_channel_id: 0 constant_flow_rate: 2 - calibration: yes + calibration: no clean: seconds: 20 diff --git a/experiments_yaml/proficiency.yml b/experiments_yaml/proficiency.yml deleted file mode 100644 index 9165804..0000000 --- a/experiments_yaml/proficiency.yml +++ /dev/null @@ -1,18 +0,0 @@ -parameters: - port: "com3" - constant_flow_channel_id: 0 - constant_flow_rate: 2 - clean_air_channel_id: 13 - - -clean: - channel_id: [13] - flow: [2] - time: 10 - pause: 10 - -proficiency: - channel_id: [1,13,2,13,3,13,4,13,5,13,6,13,7,13,8,13,9,13,10,13,11,13,12,13] - flow: [2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2] - time: 10 - pause: 10 From ee91cde90748a4f13de9bf869bff174576f51c4d Mon Sep 17 00:00:00 2001 From: carajillu Date: Thu, 31 Aug 2023 15:24:11 +0800 Subject: [PATCH 15/16] Replaced setchannel/openvalvetimed with openmultiplevalvetimed. Now it should be able to open more than 1 channel at once --- olfactometer_yaml.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/olfactometer_yaml.py b/olfactometer_yaml.py index 89d609d..47426a0 100644 --- a/olfactometer_yaml.py +++ b/olfactometer_yaml.py @@ -97,12 +97,8 @@ def run_expt(yml,ser, constant_flow_rate, constant_flow_id, calibration): # 2.2) Open the odorant channels (timed) time_ms=timeopenvalve*1000 - for channel_id in list(yml[key]["channels"].keys()): - cmd_str="setchannel "+str(channel_id) - ser_exec(ser,cmd_str) - - cmd_str="openvalvetimed "+str(time_ms) - ser_exec(ser,cmd_str) + cmd_str="openmultivalvetimed "+str(time_ms)+" "+";".join(map(str,list(yml[key]["channels"].keys()))) + ser_exec(ser,cmd_str) # 2.3) Once all commands are submitted, wait for the execution time + 10 seconds to catch up. time.sleep(timeopenvalve+10) From 9043cc9a1d7e36d040674cb565e35fd268a87cbb Mon Sep 17 00:00:00 2001 From: Smallymally <143603395+Smallymally@users.noreply.github.com> Date: Thu, 7 Sep 2023 10:07:51 +0100 Subject: [PATCH 16/16] Create Cal4at3 --- Cal4at3 | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 Cal4at3 diff --git a/Cal4at3 b/Cal4at3 new file mode 100644 index 0000000..871b61e --- /dev/null +++ b/Cal4at3 @@ -0,0 +1,13 @@ +#calibrate 4 channels + 13 to 3L/min + + +import serial +import time + +ser = serial.Serial('com3') +ser.boudrate = 9600 +ser.flush() + +print("Calibrating") +setFlow 0:3;1:3;2:3;3:3;4:3;13:3; +print("Calibration Complete")