Spaces:
Sleeping
Sleeping
File size: 6,211 Bytes
e56c4e3 b7afc5f 9937236 e56c4e3 113b2f8 dab5ad6 9d57020 e56c4e3 dab5ad6 e56c4e3 07cabd4 9f557a5 dab5ad6 9f557a5 129e6b6 dab5ad6 916acb2 07cabd4 18aba6e dab5ad6 9d57020 811d699 b298cf6 dab5ad6 b298cf6 9937236 e56c4e3 dab5ad6 e56c4e3 dab5ad6 9937236 b7afc5f dab5ad6 b7afc5f e56c4e3 9d57020 9937236 9d57020 b7afc5f e56c4e3 9d57020 dab5ad6 9d57020 b7afc5f 9937236 e56c4e3 dab5ad6 e56c4e3 b7afc5f e56c4e3 dab5ad6 e56c4e3 9d57020 95cf42c e56c4e3 95cf42c e56c4e3 dab5ad6 113b2f8 dab5ad6 113b2f8 dab5ad6 113b2f8 dab5ad6 113b2f8 9937236 e56c4e3 9d57020 18aba6e e56c4e3 dab5ad6 642cda8 9d57020 dab5ad6 e56c4e3 b7afc5f dab5ad6 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 |
import json
import queue
import time
import paho.mqtt.client as mqtt
import streamlit as st
import matplotlib.pyplot as plt
from datetime import datetime, timezone
# Initialize Streamlit app
st.title("Fan Control Panel")
# Description and context
st.markdown(
"""
This application accesses a public test demo of a small computer fan (Canakit
RPi 5 fan) controlled by a Pico W microcontroller via an EMC2101 fan controller.
Send speed commands to the motor and visualize the RPM data.
For context, see the [fan control
code](https://github.com/AccelerationConsortium/ac-training-lab/tree/main/src/ac_training_lab/picow/fan-control)
[[permalink](https://github.com/AccelerationConsortium/ac-training-lab/tree/a8b9cad0dc8c162bdff20c0d0fdff2f45c5f0012/src/ac_training_lab/picow/fan-control)]
. You may also be interested in the Acceleration Consortium's ["Hello World"
microcourse](https://ac-microcourses.readthedocs.io/en/latest/courses/hello-world/index.html)
for self-driving labs.
"""
)
# MQTT Configuration
with st.form("mqtt_form"):
HIVEMQ_HOST = st.text_input(
"Enter your HiveMQ host:",
"248cc294c37642359297f75b7b023374.s2.eu.hivemq.cloud",
type="password",
)
HIVEMQ_USERNAME = st.text_input("Enter your HiveMQ username:", "sgbaird")
HIVEMQ_PASSWORD = st.text_input(
"Enter your HiveMQ password:", "D.Pq5gYtejYbU#L", type="password"
)
PORT = st.number_input(
"Enter the port number:", min_value=1, max_value=65535, value=8883
)
PICO_ID = st.text_input("Enter your Pico ID:", "test-fan", type="password")
# Slider for fan speed
SPEED = st.slider(
"Select the Fan Speed (0-100%):", min_value=0, max_value=100, value=50
)
submit_button = st.form_submit_button(label="Send Speed Command")
# Topics
COMMAND_TOPIC = f"fan-control/picow/{PICO_ID}/speed"
SENSOR_DATA_TOPIC = f"fan-control/picow/{PICO_ID}/rpm"
# Queue for incoming sensor data
rpm_data_queue = queue.Queue()
# Initialize or load session state variables
if "rpm_data" not in st.session_state:
st.session_state.rpm_data = []
if "elapsed_times" not in st.session_state:
st.session_state.elapsed_times = []
if "initial_timestamp" not in st.session_state:
st.session_state.initial_timestamp = None
# Singleton: https://docs.streamlit.io/develop/api-reference/caching-and-state/st.cache_resource
@st.cache_resource
def create_paho_client(tls=True):
client = mqtt.Client(protocol=mqtt.MQTTv5)
if tls:
client.tls_set(tls_version=mqtt.ssl.PROTOCOL_TLS_CLIENT)
return client
# Define setup separately since sensor_data is dynamic
def setup_paho_client(
client, sensor_data_topic, hostname, username, password=None, port=8883
):
def on_message(client, userdata, msg):
rpm_data_queue.put(json.loads(msg.payload))
def on_connect(client, userdata, flags, rc, properties=None):
if rc == 0:
print("Connected successfully")
client.subscribe(sensor_data_topic, qos=1)
else:
print(f"Connection failed with code {rc}")
client.on_connect = on_connect
client.on_message = on_message
client.username_pw_set(username, password)
client.connect(hostname, port)
client.loop_start() # Start non-blocking loop
return client
def send_command(client, command_topic, msg):
print("Sending command...")
result = client.publish(command_topic, json.dumps(msg), qos=2)
result.wait_for_publish() # Ensure the message is sent
if result.rc == mqtt.MQTT_ERR_SUCCESS:
print(f"Command sent: {msg} to topic {command_topic}")
else:
print(f"Failed to send command: {result.rc}")
# Function to plot RPM data
fig, ax = plt.subplots()
def plot_rpm_data(elapsed_times, rpms, placeholder):
ax.clear() # Clear the previous plot
# Filter data to only include the last 90 seconds
current_time = elapsed_times[-1]
filtered_times = [time for time in elapsed_times if current_time - time <= 90]
filtered_rpms = rpms[-len(filtered_times) :]
ax.plot(filtered_times, filtered_rpms, marker="o")
ax.set_xlabel("Time (s)")
ax.set_ylabel("RPM")
ax.set_title("Fan RPM Over Time")
ax.xaxis.set_major_locator(plt.MaxNLocator(10)) # Show max 10 ticks on x-axis
plt.xticks(rotation=45) # Rotate x-axis labels for better readability
placeholder.pyplot(fig)
# Publish button
if submit_button:
if not PICO_ID or not HIVEMQ_HOST or not HIVEMQ_USERNAME or not HIVEMQ_PASSWORD:
st.error("Please enter all required fields.")
else:
command_msg = {"speed": SPEED}
st.info(f"Sending speed command {command_msg} to Pico ID {PICO_ID}...")
client = create_paho_client(tls=True)
client = setup_paho_client(
client,
SENSOR_DATA_TOPIC,
HIVEMQ_HOST,
HIVEMQ_USERNAME,
password=HIVEMQ_PASSWORD,
port=int(PORT),
)
send_command(client, COMMAND_TOPIC, command_msg)
# Create a placeholder for the plot
plot_placeholder = st.empty()
# Continuously fetch RPM data and update the plot
while True:
try:
data = rpm_data_queue.get(timeout=30)
rpm = data["rpm"]
current_timestamp = datetime.fromtimestamp(
data["utc_timestamp"], tz=timezone.utc
)
if st.session_state.initial_timestamp is None:
st.session_state.initial_timestamp = current_timestamp
elapsed_time = (
current_timestamp - st.session_state.initial_timestamp
).total_seconds()
st.session_state.rpm_data.append(rpm)
st.session_state.elapsed_times.append(elapsed_time)
plot_rpm_data(
st.session_state.elapsed_times,
st.session_state.rpm_data,
plot_placeholder,
)
except queue.Empty:
st.error("No sensor data received within the timeout period.")
break
# Stop the MQTT loop
client.loop_stop()
|