sgbaird commited on
Commit
9937236
·
1 Parent(s): 916acb2

update with fan control version

Browse files
Files changed (2) hide show
  1. app.py +71 -121
  2. app_filesystem_version.py +0 -227
app.py CHANGED
@@ -1,40 +1,44 @@
 
 
 
 
 
 
 
 
 
1
  import json
2
  import queue
 
 
3
 
4
  import paho.mqtt.client as mqtt
5
  import streamlit as st
6
  import matplotlib.pyplot as plt
7
  import numpy as np
8
- from matplotlib.patches import Rectangle
9
-
10
  import secrets
11
 
12
- from time import time
13
-
14
  # Initialize Streamlit app
15
- st.title("Light-mixing Control Panel")
16
 
17
  # Description and context
18
  st.markdown(
19
  """
20
- This application accesses a public test demo of a "light-mixer" located in
21
- Toronto, ON, Canada (as of 2024-07-27). Send RGB commands to a NeoPixel LED and
22
- visualize the spectral sensor data from an AS7341 sensor.
23
-
24
- For more context, you
25
- can refer to this [Colab
26
- notebook](https://colab.research.google.com/github/sparks-baird/self-driving-lab-demo/blob/main/notebooks/4.2-paho-mqtt-colab-sdl-demo-test.ipynb)
27
- and the [self-driving-lab-demo
28
- project](https://github.com/sparks-baird/self-driving-lab-demo). You may also be
29
  interested in the Acceleration Consortium's ["Hello World"
30
  microcourse](https://ac-microcourses.readthedocs.io/en/latest/courses/hello-world/index.html)
31
  for self-driving labs.
32
  """
33
  )
34
 
35
- max_power = 0.35
36
- max_value = round(max_power * 255)
37
-
38
  with st.form("mqtt_form"):
39
 
40
  # MQTT Configuration
@@ -54,34 +58,24 @@ with st.form("mqtt_form"):
54
  # User input for the Pico ID
55
  PICO_ID = st.text_input("Enter your Pico ID:", "test", type="password")
56
 
57
- # Information about the maximum power reduction
58
- st.info(
59
- f"The upper limit for RGB power levels has been set to {max_value} instead of 255. NeoPixels are bright 😎"
60
- )
61
 
62
- # Sliders for RGB values
63
- R = st.slider("Select the Red value:", min_value=0, max_value=max_value, value=0)
64
- G = st.slider("Select the Green value:", min_value=0, max_value=max_value, value=0)
65
- B = st.slider("Select the Blue value:", min_value=0, max_value=max_value, value=0)
66
 
67
- submit_button = st.form_submit_button(label="Send RGB Command")
68
-
69
- command_topic = f"sdl-demo/picow/{PICO_ID}/GPIO/28/"
70
- sensor_data_topic = f"sdl-demo/picow/{PICO_ID}/as7341/"
71
 
72
  # random session id to keep track of the session and filter out old data
73
  experiment_id = secrets.token_hex(4) # 4 bytes = 8 characters
74
- sensor_data_file = f"sensor_data-{experiment_id}.json"
75
-
76
- # TODO: Session ID using st.session_state to have history of commands and sensor data
77
 
78
- sensor_data_queue = queue.Queue()
79
 
80
 
81
  # Singleton: https://docs.streamlit.io/develop/api-reference/caching-and-state/st.cache_resource
82
  @st.cache_resource
83
  def create_paho_client(tls=True):
84
- client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, protocol=mqtt.MQTTv5)
85
  if tls:
86
  client.tls_set(tls_version=mqtt.ssl.PROTOCOL_TLS_CLIENT)
87
  return client
@@ -89,15 +83,15 @@ def create_paho_client(tls=True):
89
 
90
  # Define setup separately since sensor_data is dynamic
91
  def setup_paho_client(
92
- client, sensor_data_topic, hostname, username, password=None, port=8883
93
  ):
94
  def on_message(client, userdata, msg):
95
- sensor_data_queue.put(json.loads(msg.payload))
96
 
97
  def on_connect(client, userdata, flags, rc, properties=None):
98
  if rc != 0:
99
  print("Connected with result code " + str(rc))
100
- client.subscribe(sensor_data_topic, qos=1)
101
 
102
  client.on_connect = on_connect
103
  client.on_message = on_message
@@ -120,113 +114,69 @@ def send_command(client, command_topic, msg):
120
  print(f"Failed to send command: {result.rc}")
121
 
122
 
123
- # Helper function to plot discrete spectral sensor data
124
- def plot_spectra(sensor_data):
125
- """https://chatgpt.com/share/210d2fee-ca64-45a5-866e-e6df6e56bd1c"""
126
- wavelengths = np.array([410, 440, 470, 510, 550, 583, 620, 670])
127
- intensities = np.array(
128
- [
129
- sensor_data["ch410"],
130
- sensor_data["ch440"],
131
- sensor_data["ch470"],
132
- sensor_data["ch510"],
133
- sensor_data["ch550"],
134
- sensor_data["ch583"],
135
- sensor_data["ch620"],
136
- sensor_data["ch670"],
137
- ]
138
- )
139
-
140
- fig, ax = plt.subplots(figsize=(10, 6))
141
-
142
- num_points = 100 # for "fake" color bar effect
143
-
144
- # Adding rectangles for color bar effect
145
- dense_wavelengths = np.linspace(wavelengths.min(), wavelengths.max(), num_points)
146
- # rect_height = max(intensities) * 0.02 # Height of the rectangles
147
- rect_height = 300
148
-
149
- for dw in dense_wavelengths:
150
- rect = Rectangle(
151
- (
152
- dw - (wavelengths.max() - wavelengths.min()) / num_points / 2,
153
- -rect_height * 2,
154
- ),
155
- (wavelengths.max() - wavelengths.min()) / num_points,
156
- rect_height * 3,
157
- color=plt.cm.rainbow(
158
- (dw - wavelengths.min()) / (wavelengths.max() - wavelengths.min())
159
- ),
160
- edgecolor="none",
161
- )
162
- ax.add_patch(rect)
163
 
164
- # Main scatter plot
165
- scatter = ax.scatter(
166
- wavelengths, intensities, c=wavelengths, cmap="rainbow", edgecolor="k"
167
- )
 
168
 
169
- # Adding vertical lines from the x-axis to each point
170
- for wavelength, intensity in zip(wavelengths, intensities):
171
- ax.vlines(wavelength, 0, intensity, color="gray", linestyle="--", linewidth=1)
172
-
173
- # Adjust limits and labels with larger font size
174
- ax.set_xlim(wavelengths.min() - 10, wavelengths.max() + 10)
175
- ax.set_ylim(
176
- 0, max(30000, max(intensities) + 15)
177
- ) # Ensure the lower y limit is 0 and add buffer with a minimum upper limit of 30000
178
- ax.set_xticks(wavelengths)
179
- ax.set_xlabel("Wavelength (nm)", fontsize=14)
180
- ax.set_ylabel("Intensity", fontsize=14)
181
- ax.set_title("Spectral Intensity vs. Wavelength", fontsize=16)
182
- ax.tick_params(axis="both", which="major", labelsize=12)
183
 
 
184
  st.pyplot(fig)
185
 
186
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
187
  # Publish button
188
  if submit_button:
189
  if not PICO_ID or not HIVEMQ_HOST or not HIVEMQ_USERNAME or not HIVEMQ_PASSWORD:
190
  st.error("Please enter all required fields.")
191
  else:
192
  st.info(
193
- f"Please wait while the command {R, G, B} for experiment {experiment_id} is sent..."
194
  )
195
 
196
  client = create_paho_client(tls=True)
197
 
198
  client = setup_paho_client(
199
  client,
200
- sensor_data_topic,
201
  HIVEMQ_HOST,
202
  HIVEMQ_USERNAME,
203
  password=HIVEMQ_PASSWORD,
204
  port=int(PORT),
205
  )
206
 
207
- command_msg = {"R": R, "G": G, "B": B, "_experiment_id": experiment_id}
208
 
209
- session_timeout = time() + 60
210
  send_command(client, command_topic, command_msg)
211
-
212
- while True and time() < session_timeout:
213
-
214
- sensor_data = sensor_data_queue.get(True, timeout=15)
215
-
216
- input_message = sensor_data["_input_message"]
217
- received_experiment_id = input_message["_experiment_id"]
218
-
219
- if sensor_data and received_experiment_id == experiment_id:
220
- R1 = input_message["R"]
221
- G1 = input_message["G"]
222
- B1 = input_message["B"]
223
- st.success(
224
- f"Command {R1, G1, B1} for experiment {experiment_id} sent successfully!"
225
- )
226
- plot_spectra(sensor_data)
227
- st.write("Sensor Data Received:", sensor_data)
228
- break
229
- else:
230
- st.warning(
231
- f"Received data for experiment {received_experiment_id} instead of {experiment_id}. Retrying..."
232
- )
 
1
+ """
2
+ Fan control using EMC2101 and a Canakit RPi 5 fan
3
+
4
+ https://chatgpt.com/share/e29312a2-a589-4a07-afb8-4778506a47e9
5
+
6
+ https://github.com/AccelerationConsortium/ac-training-lab/tree/main/src/ac_training_lab/picow/fan-control
7
+
8
+ """
9
+
10
  import json
11
  import queue
12
+ import threading
13
+ import time
14
 
15
  import paho.mqtt.client as mqtt
16
  import streamlit as st
17
  import matplotlib.pyplot as plt
18
  import numpy as np
 
 
19
  import secrets
20
 
 
 
21
  # Initialize Streamlit app
22
+ st.title("Motor Control Panel")
23
 
24
  # Description and context
25
  st.markdown(
26
  """
27
+ This application accesses a public test demo of a fan controller located in Send
28
+ speed commands to the fan and visualize the RPM data.
29
+
30
+ See
31
+ [ac_training_lab/picow/fan-control](https://github.com/AccelerationConsortium/ac-training-lab/tree/main/src/ac_training_lab/picow/fan-control) # noqa: E501
32
+ [[permalink](https://github.com/AccelerationConsortium/ac-training-lab/tree/db19bda95f2a81909da7dbcd7c5dc76e8d1ee6b7/src/ac_training_lab/picow/fan-control)] # noqa: E501
33
+ for context.
34
+
35
+ You may also be
36
  interested in the Acceleration Consortium's ["Hello World"
37
  microcourse](https://ac-microcourses.readthedocs.io/en/latest/courses/hello-world/index.html)
38
  for self-driving labs.
39
  """
40
  )
41
 
 
 
 
42
  with st.form("mqtt_form"):
43
 
44
  # MQTT Configuration
 
58
  # User input for the Pico ID
59
  PICO_ID = st.text_input("Enter your Pico ID:", "test", type="password")
60
 
61
+ # Sliders for speed values
62
+ speed = st.slider("Select the Speed value:", min_value=0, max_value=100, value=0)
 
 
63
 
64
+ submit_button = st.form_submit_button(label="Send Speed Command")
 
 
 
65
 
66
+ command_topic = f"sdl-demo/picow/{PICO_ID}/motor/speed/"
67
+ rpm_data_topic = f"sdl-demo/picow/{PICO_ID}/motor/rpm/"
 
 
68
 
69
  # random session id to keep track of the session and filter out old data
70
  experiment_id = secrets.token_hex(4) # 4 bytes = 8 characters
 
 
 
71
 
72
+ rpm_data_queue = queue.Queue()
73
 
74
 
75
  # Singleton: https://docs.streamlit.io/develop/api-reference/caching-and-state/st.cache_resource
76
  @st.cache_resource
77
  def create_paho_client(tls=True):
78
+ client = mqtt.Client(protocol=mqtt.MQTTv5)
79
  if tls:
80
  client.tls_set(tls_version=mqtt.ssl.PROTOCOL_TLS_CLIENT)
81
  return client
 
83
 
84
  # Define setup separately since sensor_data is dynamic
85
  def setup_paho_client(
86
+ client, rpm_data_topic, hostname, username, password=None, port=8883
87
  ):
88
  def on_message(client, userdata, msg):
89
+ rpm_data_queue.put(json.loads(msg.payload))
90
 
91
  def on_connect(client, userdata, flags, rc, properties=None):
92
  if rc != 0:
93
  print("Connected with result code " + str(rc))
94
+ client.subscribe(rpm_data_topic, qos=1)
95
 
96
  client.on_connect = on_connect
97
  client.on_message = on_message
 
114
  print(f"Failed to send command: {result.rc}")
115
 
116
 
117
+ # Helper function to plot RPM data
118
+ def plot_rpm_data(ax1, ax2, rpm_data):
119
+ times = [data["time"] for data in rpm_data]
120
+ rpm_values = [data["rpm"] for data in rpm_data]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
 
122
+ ax1.clear()
123
+ ax1.plot(times, rpm_values, "-o", color="blue")
124
+ ax1.set_xlabel("Time (s)", fontsize=14)
125
+ ax1.set_ylabel("RPM", fontsize=14)
126
+ ax1.set_title("Motor RPM over Time", fontsize=16)
127
 
128
+ # For any additional plots or processing in ax2, currently showing RPM data only.
129
+ ax2.clear()
130
+ ax2.plot(times, rpm_values, "-o", color="blue")
131
+ ax2.set_ylim(0, max(1.1 * max(rpm_values), 1.1)) # Set fixed y-axis limits
132
+ ax2.set_xlabel("Time (s)")
133
+ ax2.set_ylabel("RPM (scaled)")
134
+ ax2.set_title("RPM Data Stream")
 
 
 
 
 
 
 
135
 
136
+ plt.pause(0.01)
137
  st.pyplot(fig)
138
 
139
 
140
+ # Function to continuously receive RPM data
141
+ def receive_rpm_data():
142
+ rpm_data = []
143
+ fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 8))
144
+ plt.ion()
145
+
146
+ start_time = time.time()
147
+ while True:
148
+ data = rpm_data_queue.get(True)
149
+ elapsed_time = time.time() - start_time
150
+ data["time"] = elapsed_time
151
+ rpm_data.append(data)
152
+ plot_rpm_data(ax1, ax2, rpm_data)
153
+ st.write("RPM Data Received:", data)
154
+
155
+
156
+ # Start a background thread to receive RPM data
157
+ threading.Thread(target=receive_rpm_data, daemon=True).start()
158
+
159
  # Publish button
160
  if submit_button:
161
  if not PICO_ID or not HIVEMQ_HOST or not HIVEMQ_USERNAME or not HIVEMQ_PASSWORD:
162
  st.error("Please enter all required fields.")
163
  else:
164
  st.info(
165
+ f"Please wait while the command {speed} for experiment {experiment_id} is sent..."
166
  )
167
 
168
  client = create_paho_client(tls=True)
169
 
170
  client = setup_paho_client(
171
  client,
172
+ rpm_data_topic,
173
  HIVEMQ_HOST,
174
  HIVEMQ_USERNAME,
175
  password=HIVEMQ_PASSWORD,
176
  port=int(PORT),
177
  )
178
 
179
+ command_msg = {"speed": speed, "_experiment_id": experiment_id}
180
 
 
181
  send_command(client, command_topic, command_msg)
182
+ st.success(f"Command {speed} for experiment {experiment_id} sent successfully!")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app_filesystem_version.py DELETED
@@ -1,227 +0,0 @@
1
- import json
2
- import os
3
- from pathlib import Path
4
-
5
- import paho.mqtt.client as mqtt
6
- import streamlit as st
7
- import matplotlib.pyplot as plt
8
- import numpy as np
9
- from matplotlib.patches import Rectangle
10
-
11
- import secrets
12
-
13
- from time import time, sleep
14
-
15
- # Initialize Streamlit app
16
- st.title("Light-mixing Control Panel")
17
-
18
- # Description and context
19
- st.markdown(
20
- """
21
- This application accesses a public test demo located in Toronto, ON, Canada (as of 2024-07-27).
22
- For more context, you can refer to this [Colab notebook](https://colab.research.google.com/github/sparks-baird/self-driving-lab-demo/blob/main/notebooks/4.2-paho-mqtt-colab-sdl-demo-test.ipynb)
23
- and the [self-driving-lab-demo project](https://github.com/sparks-baird/self-driving-lab-demo).
24
- 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.
25
- """
26
- )
27
-
28
- max_power = 0.35
29
- max_value = round(max_power * 255)
30
-
31
- with st.form("mqtt_form"):
32
-
33
- # MQTT Configuration
34
- HIVEMQ_HOST = st.text_input(
35
- "Enter your HiveMQ host:",
36
- "248cc294c37642359297f75b7b023374.s2.eu.hivemq.cloud",
37
- type="password",
38
- )
39
- HIVEMQ_USERNAME = st.text_input("Enter your HiveMQ username:", "sgbaird")
40
- HIVEMQ_PASSWORD = st.text_input(
41
- "Enter your HiveMQ password:", "D.Pq5gYtejYbU#L", type="password"
42
- )
43
- PORT = st.number_input(
44
- "Enter the port number:", min_value=1, max_value=65535, value=8883
45
- )
46
-
47
- # User input for the Pico ID
48
- PICO_ID = st.text_input("Enter your Pico ID:", "test", type="password")
49
-
50
- # Information about the maximum power reduction
51
- st.info(
52
- f"The upper limit for RGB power levels has been set to {max_value} instead of 255. NeoPixels are bright 😎"
53
- )
54
-
55
- # Sliders for RGB values
56
- R = st.slider("Select the Red value:", min_value=0, max_value=max_value, value=0)
57
- G = st.slider("Select the Green value:", min_value=0, max_value=max_value, value=0)
58
- B = st.slider("Select the Blue value:", min_value=0, max_value=max_value, value=0)
59
-
60
- submit_button = st.form_submit_button(label="Send RGB Command")
61
-
62
- command_topic = f"sdl-demo/picow/{PICO_ID}/GPIO/28/"
63
- sensor_data_topic = f"sdl-demo/picow/{PICO_ID}/as7341/"
64
-
65
-
66
- # random session id to keep track of the session and filter out old data
67
- experiment_id = secrets.token_hex(4) # 4 bytes = 8 characters
68
- sensor_data_file = f"sensor_data-{experiment_id}.json"
69
-
70
- # TODO: Session ID using st.session_state to have history of commands and sensor data
71
-
72
- # file_path = Path(sensor_data_file)
73
- # file_path.unlink(missing_ok=True)
74
-
75
-
76
- # Singleton: https://docs.streamlit.io/develop/api-reference/caching-and-state/st.cache_resource
77
- # (on_message to be set later since filename is dynamic)
78
- @st.cache_resource
79
- def get_paho_client(
80
- sensor_data_topic, hostname, username, password=None, port=8883, tls=True
81
- ):
82
- client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, protocol=mqtt.MQTTv5)
83
-
84
- def on_connect(client, userdata, flags, rc, properties=None):
85
- if rc != 0:
86
- print("Connected with result code " + str(rc))
87
- client.subscribe(sensor_data_topic, qos=1)
88
-
89
- client.on_connect = on_connect
90
-
91
- if tls:
92
- client.tls_set(tls_version=mqtt.ssl.PROTOCOL_TLS_CLIENT)
93
- client.username_pw_set(username, password)
94
- client.connect(hostname, port)
95
- client.loop_start() # Use a non-blocking loop
96
-
97
- return client
98
-
99
-
100
- def send_and_receive(client, command_topic, msg, queue_timeout=15):
101
- print("Sending command...")
102
- result = client.publish(command_topic, json.dumps(msg), qos=2)
103
- result.wait_for_publish() # Ensure the message is sent
104
-
105
- if result.rc == mqtt.MQTT_ERR_SUCCESS:
106
- print(f"Command sent: {msg} to topic {command_topic}")
107
- else:
108
- print(f"Failed to send command: {result.rc}")
109
-
110
- timeout = time() + queue_timeout # Set timeout
111
-
112
- while True:
113
- if time() > timeout:
114
- st.error("No sensor data received within the timeout period.")
115
- return None
116
- if os.path.exists(sensor_data_file):
117
- with open(sensor_data_file, "r") as f:
118
- sensor_data = json.load(f)
119
-
120
- file_path = Path(sensor_data_file)
121
- file_path.unlink(missing_ok=True)
122
-
123
- return sensor_data
124
-
125
-
126
- # Helper function to plot discrete spectral sensor data
127
- def plot_spectra(sensor_data):
128
- """https://chatgpt.com/share/210d2fee-ca64-45a5-866e-e6df6e56bd1c"""
129
- wavelengths = np.array([410, 440, 470, 510, 550, 583, 620, 670])
130
- intensities = np.array(
131
- [
132
- sensor_data["ch410"],
133
- sensor_data["ch440"],
134
- sensor_data["ch470"],
135
- sensor_data["ch510"],
136
- sensor_data["ch550"],
137
- sensor_data["ch583"],
138
- sensor_data["ch620"],
139
- sensor_data["ch670"],
140
- ]
141
- )
142
-
143
- fig, ax = plt.subplots(figsize=(10, 6))
144
-
145
- num_points = 100 # for "fake" color bar effect
146
-
147
- # Adding rectangles for color bar effect
148
- dense_wavelengths = np.linspace(wavelengths.min(), wavelengths.max(), num_points)
149
- rect_height = max(intensities) * 0.02 # Height of the rectangles
150
-
151
- for dw in dense_wavelengths:
152
- rect = Rectangle(
153
- (
154
- dw - (wavelengths.max() - wavelengths.min()) / num_points / 2,
155
- -rect_height * 2,
156
- ),
157
- (wavelengths.max() - wavelengths.min()) / num_points,
158
- rect_height * 3,
159
- color=plt.cm.rainbow(
160
- (dw - wavelengths.min()) / (wavelengths.max() - wavelengths.min())
161
- ),
162
- edgecolor="none",
163
- )
164
- ax.add_patch(rect)
165
-
166
- # Main scatter plot
167
- scatter = ax.scatter(
168
- wavelengths, intensities, c=wavelengths, cmap="rainbow", edgecolor="k"
169
- )
170
-
171
- # Adding vertical lines from the x-axis to each point
172
- for wavelength, intensity in zip(wavelengths, intensities):
173
- ax.vlines(wavelength, 0, intensity, color="gray", linestyle="--", linewidth=1)
174
-
175
- # Adjust limits and labels with larger font size
176
- ax.set_xlim(wavelengths.min() - 10, wavelengths.max() + 10)
177
- ax.set_ylim(
178
- 0, max(intensities) + 15
179
- ) # Ensure the lower y limit is 0 and add buffer
180
- ax.set_xticks(wavelengths)
181
- ax.set_xlabel("Wavelength (nm)", fontsize=14)
182
- ax.set_ylabel("Intensity", fontsize=14)
183
- ax.set_title("Spectral Intensity vs. Wavelength", fontsize=16)
184
- ax.tick_params(axis="both", which="major", labelsize=12)
185
-
186
- st.pyplot(fig)
187
-
188
-
189
- # Publish button
190
- if submit_button:
191
- if not PICO_ID or not HIVEMQ_HOST or not HIVEMQ_USERNAME or not HIVEMQ_PASSWORD:
192
- st.error("Please enter all required fields.")
193
- else:
194
- st.info(
195
- f"Please wait while the command {R, G, B} for experiment {experiment_id} is sent..."
196
- )
197
-
198
- client = get_paho_client(
199
- sensor_data_topic,
200
- HIVEMQ_HOST,
201
- HIVEMQ_USERNAME,
202
- password=HIVEMQ_PASSWORD,
203
- port=int(PORT),
204
- tls=True,
205
- )
206
-
207
- def on_message(client, userdata, msg):
208
- with open(sensor_data_file, "w") as f:
209
- json.dump(json.loads(msg.payload), f)
210
-
211
- client.on_message = on_message
212
-
213
- command_msg = {"R": R, "G": G, "B": B}
214
- sensor_data = send_and_receive(
215
- client, command_topic, command_msg, queue_timeout=15
216
- )
217
-
218
- if sensor_data:
219
- received_cmd = sensor_data["_input_message"]
220
- R1 = received_cmd["R"]
221
- G1 = received_cmd["G"]
222
- B1 = received_cmd["B"]
223
- st.success(
224
- f"Command {R1, G1, B1} for experiment {experiment_id} sent successfully!"
225
- )
226
- plot_spectra(sensor_data)
227
- st.write("Sensor Data Received:", sensor_data)