| import gradio as gr |
| import paho.mqtt.client as mqtt |
| import os |
| import json |
| import time |
| import matplotlib.pyplot as plt |
| import numpy as np |
| from datetime import datetime |
| import pytz |
| import pandas as pd |
| import plotly.graph_objs as go |
|
|
| """ |
| This is a control panel for the Pioreactor. You can use this to control the Pioreactor. You can also view the status of the Pioreactor and view graphs of the data. To use a custom Pioreactor, click on the Custom tab. |
| |
| A demo video of the Pioreactor Control Panel is available [here](https://www.youtube.com/watch?v=Xox2XnA9cyI). |
| |
| Author: Enrui (Edison) Lin |
| """ |
|
|
|
|
| HOST = os.environ.get("host") |
| PORT = int(os.environ.get("port")) |
| USERNAME = os.environ.get("username") |
| PASSWORD = os.environ.get("password") |
| PIOREACTOR = os.environ.get("pioreactor") |
|
|
| client = None |
| client_custom = None |
|
|
| experiment = None |
| running = [] |
| temp_mqtt_auto = None |
| rpm_mqtt = None |
| led_mqtt = None |
| experiments = [] |
|
|
| temp_graph = None |
| od_graph = None |
| norm_od_graph = None |
| growth_rate_graph = None |
|
|
|
|
| def on_connect(client, userdata, flags, rc): |
| print("Connected with result code "+str(rc)) |
|
|
| def create_client(host, port, username, password): |
| print(host, port, username, password) |
| client = mqtt.Client() |
| client.username_pw_set(username, password) |
| client.tls_set(tls_version=mqtt.ssl.PROTOCOL_TLS) |
| client.on_connect = on_connect |
| client.connect(host, port) |
| client.loop_start() |
| return client |
|
|
| def on_message_worker(client, userdata, message): |
| payload = message.payload.decode("utf-8") |
| data = json.loads(payload) |
| global experiment |
| global running |
| global temp_mqtt_auto |
| global rpm_mqtt |
| global led_mqtt |
| global experiments |
| experiment = data.get("experiment", None) |
| running = data.get("running", []) |
| temp_mqtt_auto = data.get("temperature_automation", None) |
| rpm_mqtt = data.get("stirring", None) |
| led_mqtt = data.get("leds", None) |
| experiments = data.get("experiments", []) |
| |
|
|
| def store_client(): |
| global client |
| global HOST |
| global PORT |
| global USERNAME |
| global PASSWORD |
| client = create_client(HOST, PORT, USERNAME, PASSWORD) |
|
|
| def custom_client(host, port, username, password): |
| global client_custom |
| client_custom = create_client(host, port, username, password) |
| |
| def stirring(rpm, host, port, username, password, pioreactor, experiment, state): |
| global client_custom |
| if client_custom is None: |
| custom_client(host, port, username, password) |
| |
| if state == "start": |
| payload = { |
| "command": "start_stirring", |
| "experiment": experiment, |
| "reactor": pioreactor, |
| "rpm": rpm |
| } |
| client_custom.publish(f"pioreactor/control", json.dumps(payload)) |
| elif state == "stop": |
| payload = { |
| "command": "stop_stirring", |
| "experiment": experiment, |
| "reactor": pioreactor |
| } |
| client_custom.publish(f"pioreactor/control", json.dumps(payload)) |
| elif state == "update": |
| payload = { |
| "command": "update_stirring_rpm", |
| "experiment": experiment, |
| "reactor": pioreactor, |
| "rpm": rpm |
| } |
| client_custom.publish(f"pioreactor/control", json.dumps(payload)) |
| else: |
| print("Invalid state") |
|
|
| def toggle_temperature_input(selected_option): |
| |
| return gr.update(visible=selected_option == "Heat To Temp") |
|
|
| def temp_automation(temperature, host, port, username, password, pioreactor, experiment, state, option): |
| global client_custom |
| if client_custom is None: |
| custom_client(host, port, username, password) |
| |
| payload_options = "thermostat" if option == "Heat To Temp" else "only_record_temperature" |
| |
| if state == "start": |
| payload = { |
| "command": "set_temperature_automation", |
| "experiment": experiment, |
| "reactor": pioreactor, |
| "temp": temperature, |
| "automation": payload_options |
| } |
| client_custom.publish(f"pioreactor/control", json.dumps(payload)) |
| elif state == "stop": |
| payload = { |
| "command": "temp_update", |
| "experiment": experiment, |
| "reactor": pioreactor, |
| "settings": { |
| "$state": "disconnected" |
| } |
| } |
| client_custom.publish(f"pioreactor/control", json.dumps(payload)) |
| elif state == "update": |
| payload = { |
| "command": "temp_update", |
| "experiment": experiment, |
| "reactor": pioreactor, |
| "settings": { |
| "target_temperature": temperature |
| } |
| } |
| client_custom.publish(f"pioreactor/control", json.dumps(payload)) |
| elif state == "restart": |
| payload = { |
| "command": "temp_restart", |
| "experiment": experiment, |
| "reactor": pioreactor, |
| "automation": payload_options, |
| "temp": temperature |
| } |
| client_custom.publish(f"pioreactor/control", json.dumps(payload)) |
| else: |
| print("Invalid state") |
| |
| def od(host, port, username, password, pioreactor, experiment, state): |
| global client_custom |
| if client_custom is None: |
| custom_client(host, port, username, password) |
| |
| if state == "start": |
| payload = { |
| "command": "start_od_reading", |
| "experiment": experiment, |
| "reactor": pioreactor |
| } |
| client_custom.publish(f"pioreactor/control", json.dumps(payload)) |
| elif state == "stop": |
| payload = { |
| "command": "stop_od_reading", |
| "experiment": experiment, |
| "reactor": pioreactor |
| } |
| client_custom.publish(f"pioreactor/control", json.dumps(payload)) |
| else: |
| print("Invalid state") |
| |
| def grf(host, port, username, password, pioreactor, experiment, state): |
| global client_custom |
| if client_custom is None: |
| custom_client(host, port, username, password) |
| |
| if state == "start": |
| payload = { |
| "command": "start_growth_rate", |
| "experiment": experiment, |
| "reactor": pioreactor |
| } |
| client_custom.publish(f"pioreactor/control", json.dumps(payload)) |
| elif state == "stop": |
| payload = { |
| "command": "stop_growth_rate", |
| "experiment": experiment, |
| "reactor": pioreactor |
| } |
| client_custom.publish(f"pioreactor/control", json.dumps(payload)) |
| else: |
| print("Invalid state") |
|
|
| def led1fn(led1, host, port, username, password, pioreactor, experiment): |
| global client_custom |
| if client_custom is None: |
| custom_client(host, port, username, password) |
| |
| payload = { |
| "command": "set_led_intensity", |
| "experiment": experiment, |
| "reactor": pioreactor, |
| "led": "A", |
| "brightness": led1 |
| } |
| client_custom.publish(f"pioreactor/control", json.dumps(payload)) |
|
|
| def led2fn(led2, host, port, username, password, pioreactor, experiment): |
| global client_custom |
| if client_custom is None: |
| custom_client(host, port, username, password) |
| |
| payload = { |
| "command": "set_led_intensity", |
| "experiment": experiment, |
| "reactor": pioreactor, |
| "led": "B", |
| "brightness": led2 |
| } |
| client_custom.publish(f"pioreactor/control", json.dumps(payload)) |
| |
| def led3fn(led3, host, port, username, password, pioreactor, experiment): |
| global client_custom |
| if client_custom is None: |
| custom_client(host, port, username, password) |
| |
| payload = { |
| "command": "set_led_intensity", |
| "experiment": experiment, |
| "reactor": pioreactor, |
| "led": "C", |
| "brightness": led3 |
| } |
| client_custom.publish(f"pioreactor/control", json.dumps(payload)) |
| |
| def led4fn(led4, host, port, username, password, pioreactor, experiment): |
| global client_custom |
| if client_custom is None: |
| custom_client(host, port, username, password) |
| |
| payload = { |
| "command": "set_led_intensity", |
| "experiment": experiment, |
| "reactor": pioreactor, |
| "led": "D", |
| "brightness": led4 |
| } |
| client_custom.publish(f"pioreactor/control", json.dumps(payload)) |
| |
| def add_media(media, host, port, username, password, pioreactor, experiment): |
| global client_custom |
| if client_custom is None: |
| custom_client(host, port, username, password) |
| |
| payload = { |
| "command": "pump_add_media", |
| "experiment": experiment, |
| "reactor": pioreactor, |
| "volume": media |
| } |
| client_custom.publish(f"pioreactor/control", json.dumps(payload)) |
|
|
| def remove_waste(waste, host, port, username, password, pioreactor, experiment): |
| global client_custom |
| if client_custom is None: |
| custom_client(host, port, username, password) |
| |
| payload = { |
| "command": "pump_remove_media", |
| "experiment": experiment, |
| "reactor": pioreactor, |
| "volume": waste |
| } |
| client_custom.publish(f"pioreactor/control", json.dumps(payload)) |
|
|
| def cycle_media(cycle, host, port, username, password, pioreactor, experiment): |
| global client_custom |
| if client_custom is None: |
| custom_client(host, port, username, password) |
| |
| payload = { |
| "command": "circulate_media", |
| "experiment": experiment, |
| "reactor": pioreactor, |
| "duration": cycle |
| } |
| client_custom.publish(f"pioreactor/control", json.dumps(payload)) |
| |
| def void_client(): |
| global client_custom |
| if client_custom is not None: |
| client_custom.disconnect() |
| client_custom = None |
| print("Client disconnected") |
|
|
| def stirring_default(rpm, experiment, state): |
| global client |
| |
| if state == "start": |
| payload = { |
| "command": "start_stirring", |
| "experiment": experiment, |
| "reactor": PIOREACTOR, |
| "rpm": rpm |
| } |
| client.publish(f"pioreactor/control", json.dumps(payload)) |
| elif state == "stop": |
| payload = { |
| "command": "stop_stirring", |
| "experiment": experiment, |
| "reactor": PIOREACTOR |
| } |
| client.publish(f"pioreactor/control", json.dumps(payload)) |
| elif state == "update": |
| payload = { |
| "command": "update_stirring_rpm", |
| "experiment": experiment, |
| "reactor": PIOREACTOR, |
| "rpm": rpm |
| } |
| client.publish(f"pioreactor/control", json.dumps(payload)) |
| else: |
| print("Invalid state") |
| |
| def temp_automation_default(temperature, experiment, state, option): |
| payload_options = "thermostat" if option == "Heat To Temp" else "only_record_temperature" |
| |
| global client |
| |
| if state == "start": |
| payload = { |
| "command": "set_temperature_automation", |
| "experiment": experiment, |
| "reactor": PIOREACTOR, |
| "temp": temperature, |
| "automation": payload_options |
| } |
| client.publish(f"pioreactor/control", json.dumps(payload)) |
| elif state == "stop": |
| payload = { |
| "command": "temp_update", |
| "experiment": experiment, |
| "reactor": PIOREACTOR, |
| "settings": { |
| "$state": "disconnected" |
| } |
| } |
| client.publish(f"pioreactor/control", json.dumps(payload)) |
| elif state == "update": |
| payload = { |
| "command": "temp_update", |
| "experiment": experiment, |
| "reactor": PIOREACTOR, |
| "settings": { |
| "target_temperature": temperature |
| } |
| } |
| client.publish(f"pioreactor/control", json.dumps(payload)) |
| elif state == "restart": |
| payload = { |
| "command": "temp_restart", |
| "experiment": experiment, |
| "reactor": PIOREACTOR, |
| "automation": payload_options, |
| "temp": temperature |
| } |
| client.publish(f"pioreactor/control", json.dumps(payload)) |
| else: |
| print("Invalid state") |
|
|
| def relay_default(experiment, state): |
| global client |
| |
| if state == "start": |
| payload = { |
| "command": "start_relay", |
| "experiment": experiment, |
| "reactor": PIOREACTOR |
| } |
| client.publish(f"pioreactor/control", json.dumps(payload)) |
| |
| elif state == "stop": |
| payload = { |
| "command": "stop_relay", |
| "experiment": experiment, |
| "reactor": PIOREACTOR |
| } |
| client.publish(f"pioreactor/control", json.dumps(payload)) |
| else: |
| print("Invalid state") |
| |
| |
| def pinch_default(experiment, state): |
| global client |
| |
| if state == "close": |
| payload = { |
| "command": "relay_on", |
| "experiment": experiment, |
| "reactor": PIOREACTOR |
| } |
| client.publish(f"pioreactor/control", json.dumps(payload)) |
| elif state == "open": |
| payload = { |
| "command": "relay_off", |
| "experiment": experiment, |
| "reactor": PIOREACTOR |
| } |
| client.publish(f"pioreactor/control", json.dumps(payload)) |
| else: |
| print("Invalid state") |
| |
| def relay(experiment, host, port, username, password, pioreactor, state): |
| global client_custom |
| if client_custom is None: |
| custom_client(host, port, username, password) |
| |
| if state == "start": |
| payload = { |
| "command": "start_relay", |
| "experiment": experiment, |
| "reactor": pioreactor |
| } |
| client_custom.publish(f"pioreactor/control", json.dumps(payload)) |
| elif state == "stop": |
| payload = { |
| "command": "stop_relay", |
| "experiment": experiment, |
| "reactor": pioreactor |
| } |
| client_custom.publish(f"pioreactor/control", json.dumps(payload)) |
| else: |
| print("Invalid state") |
| |
| def pinch(experiment, host, port, username, password, pioreactor, state): |
| global client_custom |
| if client_custom is None: |
| custom_client(host, port, username, password) |
| |
| if state == "close": |
| payload = { |
| "command": "relay_on", |
| "experiment": experiment, |
| "reactor": pioreactor |
| } |
| client_custom.publish(f"pioreactor/control", json.dumps(payload)) |
| elif state == "open": |
| payload = { |
| "command": "relay_off", |
| "experiment": experiment, |
| "reactor": pioreactor |
| } |
| client_custom.publish(f"pioreactor/control", json.dumps(payload)) |
| else: |
| print("Invalid state") |
| |
| |
| def od_default(experiment, state): |
| global client |
| |
| if state == "start": |
| payload = { |
| "command": "start_od_reading", |
| "experiment": experiment, |
| "reactor": PIOREACTOR |
| } |
| client.publish(f"pioreactor/control", json.dumps(payload)) |
| elif state == "stop": |
| payload = { |
| "command": "stop_od_reading", |
| "experiment": experiment, |
| "reactor": PIOREACTOR |
| } |
| client.publish(f"pioreactor/control", json.dumps(payload)) |
| else: |
| print("Invalid state") |
| |
| def grf_default(experiment, state): |
| global client |
| |
| if state == "start": |
| payload = { |
| "command": "start_growth_rate", |
| "experiment": experiment, |
| "reactor": PIOREACTOR |
| } |
| client.publish(f"pioreactor/control", json.dumps(payload)) |
| elif state == "stop": |
| payload = { |
| "command": "stop_growth_rate", |
| "experiment": experiment, |
| "reactor": PIOREACTOR |
| } |
| client.publish(f"pioreactor/control", json.dumps(payload)) |
| else: |
| print("Invalid state") |
| |
| def led1fn_default(led1, experiment): |
| global client |
| |
| payload = { |
| "command": "set_led_intensity", |
| "experiment": experiment, |
| "reactor": PIOREACTOR, |
| "led": "A", |
| "brightness": led1 |
| } |
| client.publish(f"pioreactor/control", json.dumps(payload)) |
| |
| def led2fn_default(led2, experiment): |
| global client |
| |
| payload = { |
| "command": "set_led_intensity", |
| "experiment": experiment, |
| "reactor": PIOREACTOR, |
| "led": "B", |
| "brightness": led2 |
| } |
| client.publish(f"pioreactor/control", json.dumps(payload)) |
| |
| def led3fn_default(led3, experiment): |
| global client |
| |
| payload = { |
| "command": "set_led_intensity", |
| "experiment": experiment, |
| "reactor": PIOREACTOR, |
| "led": "C", |
| "brightness": led3 |
| } |
| client.publish(f"pioreactor/control", json.dumps(payload)) |
| |
| def led4fn_default(led4, experiment): |
| global client |
| |
| payload = { |
| "command": "set_led_intensity", |
| "experiment": experiment, |
| "reactor": PIOREACTOR, |
| "led": "D", |
| "brightness": led4 |
| } |
| client.publish(f"pioreactor/control", json.dumps(payload)) |
| |
| def add_media_default(media, experiment): |
| global client |
| |
| payload = { |
| "command": "pump_add_media", |
| "experiment": experiment, |
| "reactor": PIOREACTOR, |
| "volume": media |
| } |
| client.publish(f"pioreactor/control", json.dumps(payload)) |
| |
| def remove_waste_default(waste, experiment): |
| global client |
| |
| payload = { |
| "command": "pump_remove_media", |
| "experiment": experiment, |
| "reactor": PIOREACTOR, |
| "volume": waste |
| } |
| client.publish(f"pioreactor/control", json.dumps(payload)) |
| |
| def cycle_media_default(cycle, experiment): |
| global client |
| |
| payload = { |
| "command": "circulate_media", |
| "experiment": experiment, |
| "reactor": PIOREACTOR, |
| "duration": cycle |
| } |
| client.publish(f"pioreactor/control", json.dumps(payload)) |
|
|
| def get_status(host, port, username, password, pioreactor, exp): |
| global client_custom |
| if client_custom is None: |
| custom_client(host, port, username, password) |
| |
| client_custom.subscribe(f"pioreactor/{pioreactor}/worker") |
| client_custom.on_message = on_message_worker |
| global experiment |
| experiment = None |
| |
| payload = { |
| "command": "get_worker", |
| "experiment": exp, |
| "reactor": pioreactor |
| } |
| |
| client_custom.publish(f"pioreactor/control", json.dumps(payload)) |
| |
| timeout = 10 |
| start = time.time() |
| |
| while experiment is None and (time.time() - start) < timeout: |
| time.sleep(0.1) |
| |
| client_custom.unsubscribe(f"pioreactor/{pioreactor}/worker") |
| |
| return experiment, running, temp_mqtt_auto, rpm_mqtt, led_mqtt, experiments |
|
|
| def get_status_default(exp): |
| global client |
| client.subscribe(f"pioreactor/{PIOREACTOR}/worker") |
| client.on_message = on_message_worker |
| global experiment |
| experiment = None |
| |
| payload = { |
| "command": "get_worker", |
| "experiment": exp, |
| "reactor": PIOREACTOR |
| } |
| |
| client.publish(f"pioreactor/control", json.dumps(payload)) |
| |
| timeout = 10 |
| start = time.time() |
| |
| while experiment is None and (time.time() - start) < timeout: |
| time.sleep(0.1) |
| |
| client.unsubscribe(f"pioreactor/{PIOREACTOR}/worker") |
| |
| return experiment, experiment, running, temp_mqtt_auto, rpm_mqtt, led_mqtt, experiments |
|
|
|
|
| def new_experiment_default(new_exp, exp): |
| global client |
| |
| payload = { |
| "command": "new_experiment", |
| "experiment": new_exp, |
| "reactor": PIOREACTOR, |
| } |
| client.publish(f"pioreactor/control", json.dumps(payload)) |
|
|
| def remove_experiment_default(rem_exp, exp): |
| global client |
| |
| payload = { |
| "command": "delete_experiment", |
| "experiment": rem_exp, |
| "reactor": PIOREACTOR, |
| } |
| client.publish(f"pioreactor/control", json.dumps(payload)) |
|
|
| def change_experiment_default(ch_exp, exp): |
| global client |
| |
| payload = { |
| "command": "change_experiment", |
| "experiment": exp, |
| "experiment_new": ch_exp, |
| "reactor": PIOREACTOR, |
| } |
| client.publish(f"pioreactor/control", json.dumps(payload)) |
| |
| def new_experiment(new_exp, host, port, username, password, pioreactor, exp): |
| global client_custom |
| if client_custom is None: |
| custom_client(host, port, username, password) |
| |
| payload = { |
| "command": "new_experiment", |
| "experiment": new_exp, |
| "reactor": pioreactor, |
| } |
| client_custom.publish(f"pioreactor/control", json.dumps(payload)) |
| |
| def remove_experiment(rem_exp, host, port, username, password, pioreactor, exp): |
| global client_custom |
| if client_custom is None: |
| custom_client(host, port, username, password) |
| |
| payload = { |
| "command": "delete_experiment", |
| "experiment": rem_exp, |
| "reactor": pioreactor, |
| } |
| client_custom.publish(f"pioreactor/control", json.dumps(payload)) |
| |
| def change_experiment(ch_exp, host, port, username, password, pioreactor, exp): |
| global client_custom |
| if client_custom is None: |
| custom_client(host, port, username, password) |
| |
| payload = { |
| "command": "change_experiment", |
| "experiment": exp, |
| "experiment_new": ch_exp, |
| "reactor": pioreactor, |
| } |
| client_custom.publish(f"pioreactor/control", json.dumps(payload)) |
| |
| def on_reading(client, userdata, message): |
| payload = message.payload.decode("utf-8") |
| data = json.loads(payload) |
| global temp_graph |
| global od_graph |
| global norm_od_graph |
| global growth_rate_graph |
| temp_graph = data.get("temperature", None) |
| od_graph = data.get("od", None) |
| norm_od_graph = data.get("normalized_od", None) |
| growth_rate_graph = data.get("growth_rate", None) |
| |
| def get_data_default(time_scale, exp): |
| global client |
| |
| payload = { |
| "command": "get_readings", |
| "experiment": exp, |
| "filter_mod": 1, |
| "lookback": 1000000, |
| "filter_mod2": 1, |
| "lookback2": 1000000, |
| "filter_mod3": 1, |
| "lookback3": 1000000, |
| "filter_mod4": 1, |
| "lookback4": 1000000, |
| "amount": time_scale, |
| "amount2": time_scale, |
| "amount3": time_scale, |
| "amount4": time_scale, |
| "reactor": PIOREACTOR |
| } |
| |
| client.on_message = on_reading |
| client.subscribe(f"pioreactor/{PIOREACTOR}/readings") |
| |
| timeout = 10 |
| start = time.time() |
| |
| global temp_graph |
| global od_graph |
| global norm_od_graph |
| global growth_rate_graph |
| |
| temp_graph = None |
| od_graph = None |
| norm_od_graph = None |
| growth_rate_graph = None |
| |
| client.publish(f"pioreactor/control", json.dumps(payload)) |
| |
| while temp_graph is None and (time.time() - start) < timeout: |
| time.sleep(0.1) |
| |
| client.unsubscribe(f"pioreactor/{PIOREACTOR}/readings") |
| |
| if temp_graph is not None: |
| for temp in temp_graph: |
| utc_time = datetime.strptime(temp["x"], "%Y-%m-%dT%H:%M:%S.%fZ") |
| local_tz = pytz.timezone("America/New_York") |
| utc_time = utc_time.replace(tzinfo=pytz.utc) |
| local_time = utc_time.astimezone(local_tz) |
| temp["x"] = local_time.strftime("%Y-%m-%d %H:%M:%S") |
| df = pd.DataFrame(temp_graph) |
| df = df.set_index("x") |
|
|
| |
| |
| |
| |
| |
| |
| |
| plot1 = go.Figure() |
| plot1.add_trace(go.Scatter(x=df.index, y=df["y"], mode="lines", name="Temperature")) |
| plot1.update_layout(title="Temperature vs Time", xaxis_title="Time", yaxis_title="Temperature") |
| else: |
| plot1 = None |
| |
| if od_graph is not None: |
| for od in od_graph: |
| utc_time = datetime.strptime(od["x"], "%Y-%m-%dT%H:%M:%S.%fZ") |
| local_tz = pytz.timezone("America/New_York") |
| utc_time = utc_time.replace(tzinfo=pytz.utc) |
| local_time = utc_time.astimezone(local_tz) |
| od["x"] = local_time.strftime("%Y-%m-%d %H:%M:%S") |
| df = pd.DataFrame(od_graph) |
| df = df.set_index("x") |
|
|
| |
| |
| |
| |
| |
| |
| |
| plot2 = go.Figure() |
| plot2.add_trace(go.Scatter(x=df.index, y=df["y"], mode="lines", name="OD")) |
| plot2.update_layout(title="OD vs Time", xaxis_title="Time", yaxis_title="OD") |
| else: |
| plot2 = None |
| |
| |
| if norm_od_graph is not None: |
| for od in norm_od_graph: |
| utc_time = datetime.strptime(od["x"], "%Y-%m-%dT%H:%M:%S.%fZ") |
| local_tz = pytz.timezone("America/New_York") |
| utc_time = utc_time.replace(tzinfo=pytz.utc) |
| local_time = utc_time.astimezone(local_tz) |
| od["x"] = local_time.strftime("%Y-%m-%d %H:%M:%S") |
| df = pd.DataFrame(norm_od_graph) |
| df = df.set_index("x") |
| |
| |
| |
| |
| |
| |
| |
| |
| plot3 = go.Figure() |
| plot3.add_trace(go.Scatter(x=df.index, y=df["y"], mode="lines", name="Normalized OD")) |
| plot3.update_layout(title="Normalized OD vs Time", xaxis_title="Time", yaxis_title="Normalized OD") |
| else: |
| plot3 = None |
| |
| |
| if growth_rate_graph is not None: |
| for gr in growth_rate_graph: |
| utc_time = datetime.strptime(gr["x"], "%Y-%m-%dT%H:%M:%S.%fZ") |
| local_tz = pytz.timezone("America/New_York") |
| utc_time = utc_time.replace(tzinfo=pytz.utc) |
| local_time = utc_time.astimezone(local_tz) |
| gr["x"] = local_time.strftime("%Y-%m-%d %H:%M:%S") |
| df = pd.DataFrame(growth_rate_graph) |
| df = df.set_index("x") |
| |
| |
| |
| |
| |
| |
| |
| |
| plot4 = go.Figure() |
| plot4.add_trace(go.Scatter(x=df.index, y=df["y"], mode="lines", name="Growth Rate")) |
| plot4.update_layout(title="Growth Rate vs Time", xaxis_title="Time", yaxis_title="Growth Rate") |
| else: |
| plot4 = None |
|
|
| |
| return plot1, plot2, plot3, plot4 |
|
|
| def update_text(input_text): |
| return input_text |
| |
| |
| with gr.Blocks() as demo: |
| gr.Markdown("# Pioreactor Control Panel") |
| |
| with gr.Tab("Default"): |
| |
| with gr.Blocks(): |
| gr.HTML(""" |
| <a target="_blank" href="https://colab.research.google.com/github/AccelerationConsortium/ac-training-lab/blob/51-pioreactor-mqtt-and-streamlit/notebooks/Pioreactor_Example_Workflow.ipynb"> |
| <img src="https://colab.research.google.com/assets/colab-badge.svg" alt="Open In Colab"/> |
| </a> |
| """) |
| gr.Markdown("This is a control panel for the [Pioreactor](https://pioreactor.com/en-ca). You can use this to control the Pioreactor. You can also view the status of the Pioreactor and view graphs of the data. To use a custom Pioreactor, click on the Custom tab.") |
|
|
| gr.Markdown("A demo video of the Pioreactor Control Panel is available [here](https://www.youtube.com/watch?v=Xox2XnA9cyI).") |
| |
| gr.Image("pioreactor.webp") |
| |
|
|
| gr.Markdown("The experiment name is required for all commands. You can get the experiment name from the status tab. When changing experiments, remember to update the experiment name.") |
| experiment_input = gr.Textbox(label="Experiment", placeholder="Get Experiment Name By Clicking Get Status", interactive=False) |
| experiment_input.input(fn=void_client) |
| |
| client = create_client(HOST, PORT, USERNAME, PASSWORD) |
| |
| with gr.Blocks(): |
| gr.Markdown("# Status") |
| get_status_a = gr.Button("Get Status") |
| |
| with gr.Row(): |
| experiment_output = gr.Textbox(label="Experiment", interactive=False) |
| running_output = gr.Textbox(label="Running", interactive=False) |
| with gr.Row(): |
| temp_output = gr.Textbox(label="Temperature Automation", interactive=False) |
| rpm_output = gr.Textbox(label="RPM", interactive=False) |
| led_output = gr.Textbox(label="LEDs", interactive=False) |
| experiments_output = gr.Textbox(label="Experiments", interactive=False) |
| |
| get_status_a.click( |
| fn=get_status_default, |
| inputs=[experiment_input], |
| outputs=[experiment_input, experiment_output, running_output, temp_output, rpm_output, led_output, experiments_output] |
| ) |
| |
| with gr.Blocks(): |
| gr.Markdown("# Experiments") |
| |
| with gr.Row(): |
| new_experiment_a = gr.Textbox(label="New Experiment") |
| new_experiment_button = gr.Button("Add Experiment") |
| |
| with gr.Row(): |
| remove_experiment_a = gr.Textbox(label="Remove Experiment") |
| remove_experiment_button = gr.Button("Remove Experiment") |
| |
| with gr.Row(): |
| change_experiment_a = gr.Textbox(label="Change Experiment") |
| change_experiment_button = gr.Button("Change Experiment") |
| |
| new_experiment_button.click( |
| fn=new_experiment_default, |
| inputs=[new_experiment_a, experiment_input] |
| ) |
| |
| remove_experiment_button.click( |
| fn=remove_experiment_default, |
| inputs=[remove_experiment_a, experiment_input] |
| ) |
| |
| change_experiment_button.click( |
| fn=change_experiment_default, |
| inputs=[change_experiment_a, experiment_input] |
| ) |
|
|
| with gr.Blocks(): |
| gr.Markdown("# Stirring") |
| rpm_input = gr.Slider(minimum=0, maximum=2000, step=50, label="RPM") |
| |
| with gr.Row(): |
| stir_state = gr.Radio(choices=["start", "stop", "update"], label="State", value="start") |
| |
| st = gr.Button("Send Command") |
| |
| st.click( |
| fn=stirring_default, |
| inputs=[rpm_input, experiment_input, stir_state] |
| ) |
| |
| with gr.Blocks(): |
| gr.Markdown("# Temperature Automation") |
|
|
| |
| temp_option = gr.Dropdown( |
| choices=["Record Temp Only", "Heat To Temp"], |
| label="Temperature Automation", |
| value="Record Temp Only" |
| ) |
|
|
| |
| temperature_slider = gr.Slider(minimum=0, maximum=60, step=1, label="Temperature", visible=False) |
|
|
| |
| with gr.Row(): |
| temp_state = gr.Radio(choices=["start", "stop", "update", "restart"], label="State", value="start") |
| temp = gr.Button("Send Command") |
|
|
| |
| temp_option.change( |
| fn=toggle_temperature_input, |
| inputs=temp_option, |
| outputs=temperature_slider |
| ) |
| |
| temp.click( |
| fn=temp_automation_default, |
| inputs=[temperature_slider, experiment_input, temp_state, temp_option] |
| ) |
| |
| with gr.Blocks(): |
| with gr.Row(): |
| with gr.Column(): |
| gr.Markdown("# OD Reading") |
| od_state = gr.Radio(choices=["start", "stop"], label="State", value="start") |
| od_button = gr.Button("Send Command") |
| |
| od_button.click( |
| fn=od_default, |
| inputs=[experiment_input, od_state] |
| ) |
|
|
| with gr.Column(): |
| gr.Markdown("# Growth Rate") |
| gr_state = gr.Radio(choices=["start", "stop"], label="State", value="start") |
| gr_button = gr.Button("Send Command") |
| |
| gr_button.click( |
| fn=grf_default, |
| inputs=[experiment_input, gr_state] |
| ) |
| |
| with gr.Blocks(): |
| gr.Markdown("# LEDS") |
|
|
| with gr.Row(): |
| with gr.Column(): |
| gr.Markdown("### Channel A") |
| led1 = gr.Slider(minimum=0, maximum=100, step=1, label="Intensity") |
| led1_button = gr.Button("Send Command") |
| |
| led1_button.click( |
| fn=led1fn_default, |
| inputs=[led1, experiment_input] |
| ) |
| |
| with gr.Column(): |
| gr.Markdown("### Channel B") |
| led2 = gr.Slider(minimum=0, maximum=100, step=1, label="Intensity") |
| led2_button = gr.Button("Send Command") |
| |
| led2_button.click( |
| fn=led2fn_default, |
| inputs=[led2, experiment_input] |
| ) |
| |
| with gr.Column(): |
| gr.Markdown("### Channel C") |
| led3 = gr.Slider(minimum=0, maximum=100, step=1, label="Intensity") |
| led3_button = gr.Button("Send Command") |
| |
| led3_button.click( |
| fn=led3fn_default, |
| inputs=[led3, experiment_input] |
| ) |
| |
| with gr.Column(): |
| gr.Markdown("### Channel D") |
| led4 = gr.Slider(minimum=0, maximum=100, step=1, label="Intensity") |
| led4_button = gr.Button("Send Command") |
| |
| led4_button.click( |
| fn=led4fn_default, |
| inputs=[led4, experiment_input] |
| ) |
| |
| with gr.Blocks(): |
| gr.Markdown("# Dosing") |
| |
| with gr.Row(): |
| with gr.Column(): |
| gr.Markdown("### Add Media") |
| |
| add_media_ml = gr.Number(label="Media (mL)", step=1, minimum=0, maximum=14) |
| add_media_button = gr.Button("Send Command") |
| |
| add_media_button.click( |
| fn=add_media_default, |
| inputs=[add_media_ml, experiment_input] |
| ) |
| |
| with gr.Column(): |
| gr.Markdown("### Remove Waste") |
| |
| remove_waste_ml = gr.Number(label="Waste (mL)", step=1, minimum=0, maximum=20) |
| remove_waste_button = gr.Button("Send Command") |
| |
| remove_waste_button.click( |
| fn=remove_waste_default, |
| inputs=[remove_waste_ml, experiment_input] |
| ) |
| |
| with gr.Column(): |
| gr.Markdown("### Cycle Media") |
| |
| cycle_media_sec = gr.Number(label="Cycle (s)", step=1, minimum=0, maximum=60) |
| cycle_media_button = gr.Button("Send Command") |
| |
| cycle_media_button.click( |
| fn=cycle_media_default, |
| inputs=[cycle_media_sec, experiment_input] |
| ) |
| |
| with gr.Blocks(): |
| gr.Markdown("# Pinch Valve") |
| |
| gr.Markdown("Ensure that relay is running in Status. If not, start the automation before using the pinch valve.") |
| |
| with gr.Row(): |
| with gr.Column(): |
| gr.Markdown("### Relay Automation") |
| |
| relay_state = gr.Radio(choices=["start", "stop"], label="State", value="start") |
| relay_button = gr.Button("Send Command") |
| |
| relay_button.click( |
| fn=relay_default, |
| inputs=[experiment_input, relay_state] |
| ) |
| |
| with gr.Column(): |
| gr.Markdown("### Pinch Valve") |
| |
| pinch_state = gr.Radio(choices=["open", "close"], label="State", value="open") |
| pinch_button = gr.Button("Send Command") |
| |
| pinch_button.click( |
| fn=pinch_default, |
| inputs=[experiment_input, pinch_state] |
| ) |
| |
| |
| with gr.Tab("Default Graphs"): |
| with gr.Row(): |
| time_scale = gr.Radio(label="Time Scale", choices=["1 hour", "24 hour", "All Data"], value="All Data") |
| get_graphs = gr.Button("Get Graphs") |
| |
| plot1 = gr.Plot() |
| plot2 = gr.Plot() |
| plot3 = gr.Plot() |
| plot4 = gr.Plot() |
| |
| get_graphs.click( |
| fn=get_data_default, |
| inputs=[time_scale, experiment_input], |
| outputs=[plot1, plot2, plot3, plot4] |
| ) |
| |
|
|
| with gr.Tab("Custom"): |
| gr.Markdown("Remember to change the experiment name when changing experiments.") |
| |
| with gr.Row(): |
| host_input = gr.Textbox(label="Host") |
| port_input = gr.Number(label="Port") |
| |
| host_input.input(fn=void_client) |
| port_input.input(fn=void_client) |
| |
| with gr.Row(): |
| username_input = gr.Textbox(label="Username") |
| password_input = gr.Textbox(label="Password") |
| pioreactor_input = gr.Textbox(label="Pioreactor") |
| experiment_input = gr.Textbox(label="Experiment") |
| |
| username_input.input(fn=void_client) |
| password_input.input(fn=void_client) |
| pioreactor_input.input(fn=void_client) |
| experiment_input.input(fn=void_client) |
|
|
| with gr.Blocks(): |
| gr.Markdown("# Status") |
| get_status_b = gr.Button("Get Status") |
| |
| with gr.Row(): |
| experiment_output = gr.Textbox(label="Experiment", interactive=False) |
| running_output = gr.Textbox(label="Running", interactive=False) |
| with gr.Row(): |
| temp_output = gr.Textbox(label="Temperature Automation", interactive=False) |
| rpm_output = gr.Textbox(label="RPM", interactive=False) |
| led_output = gr.Textbox(label="LEDs", interactive=False) |
| experiments_output = gr.Textbox(label="Experiments", interactive=False) |
| |
| get_status_b.click( |
| fn=get_status, |
| inputs=[host_input, port_input, username_input, password_input, pioreactor_input, experiment_input], |
| outputs=[experiment_output, running_output, temp_output, rpm_output, led_output, experiments_output] |
| ) |
| |
| with gr.Blocks(): |
| gr.Markdown("# Experiments") |
| |
| with gr.Row(): |
| new_experiment_b = gr.Textbox(label="New Experiment") |
| new_experiment_button_b = gr.Button("Add Experiment") |
| |
| with gr.Row(): |
| remove_experiment_b = gr.Textbox(label="Remove Experiment") |
| remove_experiment_button_b = gr.Button("Remove Experiment") |
| |
| with gr.Row(): |
| change_experiment_b = gr.Textbox(label="Change Experiment") |
| change_experiment_button_b = gr.Button("Change Experiment") |
| |
| new_experiment_button_b.click( |
| fn=new_experiment, |
| inputs=[new_experiment_b, host_input, port_input, username_input, password_input, pioreactor_input, experiment_input] |
| ) |
| |
| remove_experiment_button_b.click( |
| fn=remove_experiment, |
| inputs=[remove_experiment_b, host_input, port_input, username_input, password_input, pioreactor_input, experiment_input] |
| ) |
| |
| change_experiment_button_b.click( |
| fn=change_experiment, |
| inputs=[change_experiment_b, host_input, port_input, username_input, password_input, pioreactor_input, experiment_input] |
| ) |
|
|
| with gr.Blocks(): |
| gr.Markdown("# Stirring") |
| rpm_input = gr.Slider(minimum=0, maximum=2000, step=50, label="RPM") |
| |
| with gr.Row(): |
| stir_state = gr.Radio(choices=["start", "stop", "update"], label="State", value="start") |
| |
| st = gr.Button("Send Command") |
| |
| st.click( |
| fn=stirring, |
| inputs=[rpm_input, host_input, port_input, username_input, password_input, pioreactor_input, experiment_input, stir_state] |
| ) |
| |
| with gr.Blocks(): |
| gr.Markdown("# Temperature Automation") |
|
|
| |
| temp_option = gr.Dropdown( |
| choices=["Record Temp Only", "Heat To Temp"], |
| label="Temperature Automation", |
| value="Record Temp Only" |
| ) |
|
|
| |
| temperature_slider = gr.Slider(minimum=0, maximum=60, step=1, label="Temperature", visible=False) |
|
|
| |
| with gr.Row(): |
| temp_state = gr.Radio(choices=["start", "stop", "update", "restart"], label="State", value="start") |
| temp = gr.Button("Send Command") |
|
|
| |
| temp_option.change( |
| fn=toggle_temperature_input, |
| inputs=temp_option, |
| outputs=temperature_slider |
| ) |
| |
| temp.click( |
| fn=temp_automation, |
| inputs=[temperature_slider, host_input, port_input, username_input, password_input, pioreactor_input, experiment_input, temp_state, temp_option] |
| ) |
|
|
| with gr.Blocks(): |
| with gr.Row(): |
| with gr.Column(): |
| gr.Markdown("# OD Reading") |
| od_state = gr.Radio(choices=["start", "stop"], label="State", value="start") |
| od_button = gr.Button("Send Command") |
| |
| od_button.click( |
| fn=od, |
| inputs=[host_input, port_input, username_input, password_input, pioreactor_input, experiment_input, od_state] |
| ) |
|
|
| with gr.Column(): |
| gr.Markdown("# Growth Rate") |
| gr_state = gr.Radio(choices=["start", "stop"], label="State", value="start") |
| gr_button = gr.Button("Send Command") |
| |
| gr_button.click( |
| fn=grf, |
| inputs=[host_input, port_input, username_input, password_input, pioreactor_input, experiment_input, gr_state] |
| ) |
| |
| with gr.Blocks(): |
| gr.Markdown("# LEDS") |
|
|
| with gr.Row(): |
| with gr.Column(): |
| gr.Markdown("### Channel A") |
| led1 = gr.Slider(minimum=0, maximum=100, step=1, label="Intensity") |
| led1_button = gr.Button("Send Command") |
| |
| led1_button.click( |
| fn=led1fn, |
| inputs=[led1, host_input, port_input, username_input, password_input, pioreactor_input, experiment_input] |
| ) |
| |
| with gr.Column(): |
| gr.Markdown("### Channel B") |
| led2 = gr.Slider(minimum=0, maximum=100, step=1, label="Intensity") |
| led2_button = gr.Button("Send Command") |
| |
| led2_button.click( |
| fn=led2fn, |
| inputs=[led2, host_input, port_input, username_input, password_input, pioreactor_input, experiment_input] |
| ) |
| |
| with gr.Column(): |
| gr.Markdown("### Channel C") |
| led3 = gr.Slider(minimum=0, maximum=100, step=1, label="Intensity") |
| led3_button = gr.Button("Send Command") |
| |
| led3_button.click( |
| fn=led3fn, |
| inputs=[led3, host_input, port_input, username_input, password_input, pioreactor_input, experiment_input] |
| ) |
| |
| with gr.Column(): |
| gr.Markdown("### Channel D") |
| led4 = gr.Slider(minimum=0, maximum=100, step=1, label="Intensity") |
| led4_button = gr.Button("Send Command") |
| |
| led4_button.click( |
| fn=led4fn, |
| inputs=[led4, host_input, port_input, username_input, password_input, pioreactor_input, experiment_input] |
| ) |
| |
| with gr.Blocks(): |
| gr.Markdown("# Dosing") |
| |
| with gr.Row(): |
| with gr.Column(): |
| gr.Markdown("### Add Media") |
| |
| add_media_ml = gr.Number(label="Media (mL)", step=1, minimum=0, maximum=14) |
| add_media_button = gr.Button("Send Command") |
| |
| add_media_button.click( |
| fn=add_media, |
| inputs=[add_media_ml, host_input, port_input, username_input, password_input, pioreactor_input, experiment_input] |
| ) |
| |
| with gr.Column(): |
| gr.Markdown("### Remove Waste") |
| |
| remove_waste_ml = gr.Number(label="Waste (mL)", step=1, minimum=0, maximum=20) |
| remove_waste_button = gr.Button("Send Command") |
| |
| remove_waste_button.click( |
| fn=remove_waste, |
| inputs=[remove_waste_ml, host_input, port_input, username_input, password_input, pioreactor_input, experiment_input] |
| ) |
| |
| with gr.Column(): |
| gr.Markdown("### Cycle Media") |
| |
| cycle_media_sec = gr.Number(label="Cycle (s)", step=1, minimum=0, maximum=60) |
| cycle_media_button = gr.Button("Send Command") |
| |
| cycle_media_button.click( |
| fn=cycle_media, |
| inputs=[cycle_media_sec, host_input, port_input, username_input, password_input, pioreactor_input, experiment_input] |
| ) |
| |
|
|
| with gr.Blocks(): |
| gr.Markdown("# Pinch Valve") |
| |
| gr.Markdown("Ensure that relay is running in Status. If not, start the automation before using the pinch valve.") |
| |
| with gr.Row(): |
| with gr.Column(): |
| gr.Markdown("### Relay Automation") |
| |
| relay_state = gr.Radio(choices=["start", "stop"], label="State", value="start") |
| relay_button = gr.Button("Send Command") |
| |
| relay_button.click( |
| fn=relay, |
| inputs=[host_input, port_input, username_input, password_input, pioreactor_input, experiment_input, relay_state] |
| ) |
| |
| with gr.Column(): |
| gr.Markdown("### Pinch Valve") |
| |
| pinch_state = gr.Radio(choices=["open", "close"], label="State", value="open") |
| pinch_button = gr.Button("Send Command") |
| |
| pinch_button.click( |
| fn=pinch, |
| inputs=[host_input, port_input, username_input, password_input, pioreactor_input, experiment_input, pinch_state] |
| ) |
|
|
|
|
| |
| demo.launch() |