Neil-YL commited on
Commit
5fffc24
·
verified ·
1 Parent(s): 0aab7de

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +201 -0
app.py ADDED
@@ -0,0 +1,201 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from queue import Queue
3
+ import threading
4
+ import time
5
+ import paho.mqtt.client as mqtt
6
+ import json
7
+ import secrets
8
+ from well_status_utils import find_unused_wells, update_used_wells
9
+
10
+
11
+ # Task Queue
12
+ task_queue = Queue()
13
+ result_queue = Queue()
14
+ current_task = None
15
+
16
+ # MQTT Configuration
17
+ MQTT_BROKER = "248cc294c37642359297f75b7b023374.s2.eu.hivemq.cloud"
18
+ MQTT_PORT = 8883
19
+ MQTT_USERNAME = "sgbaird"
20
+ MQTT_PASSWORD = "D.Pq5gYtejYbU#L"
21
+
22
+
23
+ OT2_SERIAL = "OT2CEP20240218R04"
24
+ PICO_ID = "e66130100f895134"
25
+
26
+ OT2_COMMAND_TOPIC = f"command/ot2/{OT2_SERIAL}/pipette"
27
+ OT2_STATUS_TOPIC = f"status/ot2/{OT2_SERIAL}/complete"
28
+ SENSOR_COMMAND_TOPIC = f"command/picow/{PICO_ID}/as7341/read"
29
+ SENSOR_DATA_TOPIC = f"color-mixing/picow/{PICO_ID}/as7341"
30
+
31
+
32
+ # MQTT Client
33
+ mqtt_client = mqtt.Client()
34
+ mqtt_client.tls_set(tls_version=mqtt.ssl.PROTOCOL_TLS_CLIENT) # Enable TLS
35
+ mqtt_client.username_pw_set(MQTT_USERNAME, MQTT_PASSWORD)
36
+
37
+ # MQTT Handlers
38
+ def on_connect(client, userdata, flags, rc):
39
+ print("Connected to MQTT Broker with result code", rc)
40
+ client.subscribe([(OT2_STATUS_TOPIC,2),(SENSOR_DATA_TOPIC,2)])
41
+
42
+ def on_message(client, userdata, msg):
43
+ global current_task
44
+ global sensor_results
45
+
46
+ try:
47
+ payload = json.loads(msg.payload.decode("utf-8"))
48
+ if msg.topic == OT2_STATUS_TOPIC:
49
+ handle_sensor_status(payload)
50
+
51
+ elif msg.topic == SENSOR_DATA_TOPIC:
52
+ print("Sensor: Data received.")
53
+ print(payload)
54
+ sensor_results = payload
55
+ mqtt_client.publish(
56
+ OT2_COMMAND_TOPIC,
57
+ json.dumps({"command": {"sensor_status": "read"}, "experiment_id": payload["experiment_id"],"session_id": payload["session_id"]}),
58
+ )
59
+ print("Sending sensor to charging position.")
60
+
61
+ except Exception as e:
62
+ print("Error processing MQTT message:", e)
63
+
64
+ mqtt_client.on_connect = on_connect
65
+ mqtt_client.on_message = on_message
66
+ mqtt_client.connect(MQTT_BROKER, MQTT_PORT)
67
+ mqtt_client.loop_start()
68
+
69
+ # Task processing logic
70
+ def add_to_queue(student_id, R, Y, B):
71
+ global current_task
72
+
73
+ # Ensure total volume is 300 µL
74
+ if R + Y + B != 300:
75
+ return {"Status": "Error", "Message": "The total R, Y, and B volume must be exactly 300 µL."}
76
+
77
+ experiment_id = secrets.token_hex(4)
78
+
79
+ try:
80
+ # Find an available well on the plate
81
+ empty_wells = find_unused_wells()
82
+ if not empty_wells:
83
+ raise ValueError("No available wells for the experiment.")
84
+ selected_well = empty_wells[0]
85
+ # Execute OT-2 protocol to mix the RYB proportions
86
+ update_used_wells([selected_well]) # Mark the well as used
87
+
88
+ except Exception as e:
89
+ yield {
90
+ "Status": "Error",
91
+ "Message": f"Experiment execution failed: {e}"
92
+ }
93
+ return
94
+
95
+ # Add task to queue
96
+ task = {
97
+ "R": R,
98
+ "Y": Y,
99
+ "B": B,
100
+ "well":selected_well,
101
+ "session_id": student_id,
102
+ "experiment_id": experiment_id,
103
+ "status": "queued",
104
+ }
105
+ task_queue.put(task)
106
+
107
+ # Monitor queue position
108
+ while task in list(task_queue.queue):
109
+ queue_position = list(task_queue.queue).index(task) + 1
110
+ time.sleep(1) # Simulate periodic updates
111
+ yield {
112
+ "Status": "Queued",
113
+ "Message": f"Your experiment is queued. Current position: {queue_position}",
114
+ "Student ID": student_id,
115
+ "RYB Volumes (µL)": {"R": R, "Y": Y, "B": B},
116
+ "well":selected_well,
117
+ }
118
+
119
+ # When processing starts
120
+ yield {"Status": "In Progress", "Message": "Experiment is running..."}
121
+
122
+ result = result_queue.get() # This will block until the result is available
123
+ yield result # Return the updated result to the Gradio interface
124
+
125
+ # Start Task Processor Thread
126
+ def task_processor():
127
+ global current_task
128
+ while True:
129
+ if not current_task and not task_queue.empty():
130
+ current_task = task_queue.get()
131
+ current_task["status"] = "processing"
132
+ mqtt_client.publish(
133
+ OT2_COMMAND_TOPIC,
134
+ json.dumps({
135
+ "command": {"R": current_task["R"], "Y": current_task["Y"], "B": current_task["B"], "well": current_task["well"]}, "experiment_id": current_task["experiment_id"],
136
+ "session_id": current_task["session_id"]
137
+ }),
138
+ )
139
+ time.sleep(1)
140
+
141
+ def handle_sensor_status(payload):
142
+ global current_task
143
+ global sensor_results
144
+ if "in_place" in json.dumps(payload):
145
+ print("OT-2: Sensor in place. Sending read command to sensor.")
146
+ mqtt_client.publish(SENSOR_COMMAND_TOPIC,
147
+ json.dumps({
148
+ "command": {
149
+ "R": current_task["R"],
150
+ "Y": current_task["Y"],
151
+ "B": current_task["B"],
152
+ "well": current_task["well"]
153
+ },
154
+ "experiment_id": current_task["experiment_id"],
155
+ "session_id": current_task["session_id"]
156
+ })
157
+ )
158
+
159
+ elif payload["status"]["sensor_status"] == "charging":
160
+ print("OT-2: Sensor returned to charging position.")
161
+ # Push result to result_queue
162
+ result_queue.put({
163
+ "Message": "Experiment completed!",
164
+ "Student ID": current_task["session_id"],
165
+ "Command": {
166
+ "R": current_task["R"],
167
+ "Y": current_task["Y"],
168
+ "B": current_task["B"],
169
+ "well": current_task["well"],
170
+ },
171
+ "Sensor Data": sensor_results["sensor_data"],
172
+ "Experiment ID": current_task["experiment_id"]
173
+
174
+ })
175
+
176
+ current_task = None
177
+
178
+
179
+
180
+ processor_thread = threading.Thread(target=task_processor, daemon=True)
181
+ processor_thread.start()
182
+
183
+ # Define the Gradio interface
184
+ inputs = [
185
+ gr.Textbox(label="Student ID", placeholder="Enter your unique ID"),
186
+ gr.Slider(1, 300, step=1, label="Red (R) Volume (µL)"),
187
+ gr.Slider(1, 300, step=1, label="Yellow (Y) Volume (µL)"),
188
+ gr.Slider(1, 300, step=1, label="Blue (B) Volume (µL)")
189
+ ]
190
+ outputs = gr.JSON()
191
+
192
+ app = gr.Interface(
193
+ fn=add_to_queue,
194
+ inputs=inputs,
195
+ outputs=outputs,
196
+ title="OT-2 Liquid Color Matching Experiment Queue",
197
+ description="Enter R, Y, and B volumes (in µL). Ensure the total volume is exactly 300 µL.",
198
+ flagging_mode="never"
199
+ )
200
+
201
+ app.launch()