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()