Today: Discussion + demo then hands-on lab
Predict what the next serial line should look like before you run it. Run Serial Monitor or the script. Investigate mismatches at the stage boundary (wrong columns? wrong type?). Modify one filter at a time, re-run. Make a one-line contract change if the hardware reality changed. (Full lab walkthrough uses the same loop.)
"Should I go study in the GIX room right now? Is it comfortable? Is someone already there?"
A few sensors and Python code can answer that question. Functionally, it is identical to what a building management system asks 10,000 times per hour.
Before jumping to code, define three measurable requirements for the GIX lounge monitor:
These three numbers drive every architectural decision in the rest of this lecture. This is the 40% planning in the 40/20/40 principle.
Create a Streamlit web app. The app should set a page title about a “GIX lounge comfort preview”, add a short markdown subtitle, and display a line of plain text saying hello.
Typical reply includes st.title, st.markdown, st.write (or similar). Read the code before you run it.
In the same app.py for the GIX lounge comfort preview, keep the page title, subtitle, and hello line from Prompt 1. Below that, add a slider for target comfort from 0 to 100 (0 = chilly, 100 = cozy) with default 50, and show the chosen value below the slider using st.write or st.metric. In a one-line comment in the code, note that Streamlit reruns the whole script top-to-bottom when the comfort slider moves.
In the same GIX lounge comfort preview app, keep the title, subtitle, hello, and comfort slider from Prompts 1–2. Add three more widgets in the main area: st.checkbox labeled “Simulate occupancy” (default False); st.radio for lighting mood with options Warm glow, Neutral, Cool white; and st.text_input for an optional short note (placeholder like “Note for this preview”). Show the current values under the widgets with st.write or st.caption. Add a short comment that changing any widget triggers a rerun.
Refactor the same GIX lounge comfort preview app.py: keep the page title and markdown subtitle from Prompt 1, plus the hello line, comfort slider, and the checkbox/radio/text widgets from Prompts 2–2b. Use st.columns: in the left column show a metric “Feels like (demo °F)” with a number (placeholder or a simple function of the comfort slider); in the right column show “Humidity (demo %)” with a number. Put st.selectbox in the sidebar for “Quiet zone” vs “Collaborative” (labels only). Keep the hello line, comfort slider, and extra widgets below the columns or in a sensible place.
Build a small DataFrame with fake timestamps and a temperature column (10–20 rows). The temperature column should depend on the slider (e.g. base room temp plus a slider-driven offset or small noise). Display st.line_chart for temperature vs index or time. Optionally show st.dataframe with head() if it still fits on screen.
Same chart APIs apply once real readings live in a DataFrame. Next: the pipeline that gets them there.
Each time you interact with a widget (slider, sidebar, checkbox, …), Streamlit runs your script from top to bottom again. That is the normal “refresh”: a new run, same file, new widget state.
st.rerun() triggers the same kind of full rerun programmatically — for example after you update st.session_state and want the page to redraw without waiting for another click.
st.empty() reserves a placeholder. You can replace only that slot (e.g. with placeholder.container(): or placeholder.write(...)) when data arrives on a timer or stream — so the dashboard can feel live without relying on a widget event every time.
Contrast: a plain while True that redraws everything can fight Streamlit’s rerun model; prefer updating a bounded buffer (e.g. deque(maxlen=20)) inside a controlled loop paired with a placeholder.
import streamlit as st
import pandas as pd
@st.cache_data
def load_rows(csv_path: str) -> pd.DataFrame:
return pd.read_csv(csv_path) # slow I/O; skipped when path unchanged
if "ticks" not in st.session_state:
st.session_state.ticks = 0
live = st.empty()
df = load_rows("sample.csv")
live.metric("Cached rows", len(df))
if st.button("Increment + rerun"):
st.session_state.ticks += 1
st.rerun() # restarts script from top (like a widget-driven rerun)
st.caption(f"Session ticks: {st.session_state.ticks}")
Cache status: decorate slow work with @st.cache_data (serializable return values) or @st.cache_resource (connections, models). On rerun, Streamlit skips the cached body when inputs are unchanged. Call st.cache_data.clear() to invalidate if the file behind csv_path changes on disk.
Same stages, two vocabularies: elsewhere we say “parse & validate” — that work spans Stage 5 (decode bytes, split CSV fields) and Stage 6 (check ranges/types, build a DataFrame). When debugging, ask which of those two filters broke the contract.
This 9-stage pipeline is a typical pipe-and-filter architecture: independent processing steps connected by explicit data contracts.
Debug mindset: Trace stage-by-stage. In our course pipeline: sensor → formatter → Stage 5 parse → Stage 6 validate → chart. Ask “which filter broke the contract?”, not just “why is the chart wrong?”
Even successful readings fluctuate: thermal noise, ADC quantization, acoustic interference. This embedded dashboard is intentionally “alive” to show that micro-variation is normal.
This is not a bug. This is physics. Software response:
df['temp_smoothed'] = df['temperature_c'].rolling(window=5).mean()
±0.5°C (sensor) + ±0.05°C (quantization) = dashboard cannot claim better than ±0.6°C
“22.5°C” really means [21.9, 23.1]
window=5 at 2s intervals = 10-second smoothing. Is that too slow for a sudden spike? You decide.
The CSV “format contract” deserves a written specification, not a passing mention:
Field 1: timestamp — float, seconds since boot, [0, ∞), monotonic
Field 2: temp_c — float, °C, [-40, 80], resolution 0.1
Field 3: humid_pct — float, %, [0, 100], resolution 0.1
Field 4: dist_cm — float, cm, [2, 400], -1.0 = sensor error
Delimiter: comma | Terminator: \n | Encoding: UTF-8
Rate: 1 line / 2s ±100ms
This contract is the primary artifact.
Interface Contract: This CSV spec is the first of many contracts: DataFrame asserts (W4), Zod schemas (W6), tool input_schema (W7). Same idea: define what crosses a boundary, verify it.
A chart that passes all three verification points is a chart you can trust. A chart that skips them is a chart that looks trustworthy.
"A chart that renders without error is NOT the same as a chart that shows correct data. This is a typical dangerous assumption beginners make."
The pipeline model is your defense against this.
The hardest failure: system looks healthy but shows stale data.
# Freshness: set once before the loop; refresh only on successful reads
last_updated = time.time()
while True:
reading = get_latest_reading()
if reading:
last_updated = time.time()
# ... render metrics ...
age = time.time() - last_updated
color = "green" if age < 10 else "red"
st.markdown(f":{color}[Last update: {age:.0f}s ago]")
Three failure strategies in 6 lines: silent (pass), logged (logging.warning), counted with threshold. Choose the most observable strategy your system can support.
In Lab 3 you will work with physical sensors and a microcontroller. Visit the makerspace and talk with the crew there to check out the hardware you need.
Your choice: Which sensors you use is up to you — pick something that fits what you want to measure.
Plan ahead: know what you want to measure (or a short list of options) before you go, so checkout goes smoothly.