Build a Temperature Monitoring System

Simulate, analyze, and report on sensor data using Python fundamentals.

Introduction

30 Second Summary

Every sensor in a smart building, weather station, or factory floor does the same thing. It takes readings, watches for danger, and sounds the alarm when something goes wrong. The logic behind that entire process is surprisingly simple.

In this project, you will build a temperature monitoring system in Python that simulates sensor data, calculates statistics, flags dangerous readings, and produces a clean professional report.

What You'll Build

You run one command and instantly see 24 hours of simulated temperature data scroll across your terminal, followed by a statistical summary and alerts flagging the exact moments things went wrong.

By the end of this project, you'll have:

  • A working sensor simulator that generates a full day of realistic temperature readings every time you run it.
  • Automatic statistical analysis that calculates min, max, and average temperatures so you can spot overall trends.
  • A threshold-based alert system that pinpoints exactly when and where temperatures went dangerously out of range.
  • Secret Mission: Add a humidity sensor and export all data to a CSV file you can open in Excel.

Are there any prerequisites?

None. This guide includes setup for all required tools. You just need a computer with VS Code and Python installed, which Step 1 walks you through.

Before We Start

Before we dive in, let's lock in what you're about to build and why it matters for your mechatronics path.

Set Up Your Project

Every Python project starts with two things: a folder to hold your files and a script file to write your code in.

In this step, you'll create a project folder on your Desktop, set up your first Python file in VS Code, and confirm Python runs correctly on your machine.

In this step, get ready to:
  • Create a temp-monitor project folder on your Desktop and open it in VS Code.
  • Write and run your first Python script in the VS Code terminal.
Create your project folder and file
  • Press the Windows key to open your search bar.
  • Type VS Code and press Enter to open it.
  • Click File in the top menu bar.
  • Click Open Folder.
  • Navigate to your Desktop in the folder picker.
  • Click New Folder, type temp-monitor, and press Enter.
  • With temp-monitor selected, click Select Folder to open it in VS Code.

You should see TEMP-MONITOR appear in the Explorer sidebar on the left.

  • Hover over the TEMP-MONITOR heading in the Explorer sidebar.
  • Click the New File icon (a page with a plus sign).
  • Type sensor_simulator.py and press Enter.

The file opens in the editor area, ready for you to type in.

Write and run your first script

The simplest way to confirm Python can find and execute your file is to print a message to the terminal.

  • Add this line to sensor_simulator.py:
print("Hello from sensor simulator!")

What does this code do?

print() sends whatever text is inside the parentheses to your terminal. This one line is enough to prove Python can find your file, read it, and run it successfully.

  • Save the file by pressing Ctrl+S.
  • Click Terminal in the top menu bar.
  • Click New Terminal.

A terminal panel appears at the bottom of VS Code. Before you run the next command, what do you think will appear in the terminal?

  • Run your script by entering this command:
python sensor_simulator.py

You should see Hello from sensor simulator! printed in the terminal.

Seeing 'python' is not recognized?

Try running python3 sensor_simulator.py instead. Some systems register the command as python3 rather than python.

Use whichever command works for the rest of this project.

help me fix Python not being recognized in my VS Code terminal on Windows

✔️ Awesome, I've got everything!

Great. Double check you've saved sensor_simulator.py and move on to the next step.

ⓧ I'd like to double check the full code

Your sensor_simulator.py should contain exactly this:

print("Hello from sensor simulator!")

Your project is set up and Python is running. Next up, you'll generate simulated sensor data that mimics what a real temperature sensor would produce.

Generate Simulated Sensor Data

Your environment is locked in and ready to go. Now it's time to give your monitoring system something to monitor.

Real sensors like those on your Arduino or Pico produce streams of numbers over time. Before you can analyze anything, you need data. This step creates that same data pattern using Python's random module, so you have something tangible to work with immediately.

In this step, get ready to:
  • Import Python's random and datetime modules.
  • Write a function that generates 24 simulated temperature readings.
  • Print the readings to see raw sensor data in your terminal.
Import modules and write the data generator

Your script currently just prints a hello message. You need to replace that with code that simulates a full day of sensor readings.

  • In sensor_simulator.py, select all the existing code and delete it.
  • Replace it with the following imports, configuration, and function:
import random
from datetime import datetime, timedelta

# --- Configuration ---
NUM_READINGS = 24
BASE_TEMP = 22.0
VARIATION = 8.0
MIN_SAFE = 18.0
MAX_SAFE = 30.0


def generate_readings(num_readings, base_temp, variation):
    """Simulate temperature sensor readings over time."""
    readings = []
    start_time = datetime(2026, 7, 11, 0, 0, 0)

    for i in range(num_readings):
        timestamp = start_time + timedelta(hours=i)
        temp = base_temp + random.uniform(-variation, variation)
        temp = round(temp, 1)
        readings.append({"timestamp": timestamp, "temperature": temp})

    return readings

What does this code do?

  • random gives you random.uniform(a, b) which returns a random decimal number between a and b. This simulates natural temperature fluctuation.
  • datetime and timedelta let you create timestamps and add hours to them, so each reading gets a time like 00:00, 01:00, 02:00.
  • The configuration variables at the top (NUM_READINGS, BASE_TEMP, etc.) define the simulation parameters. MIN_SAFE and MAX_SAFE will be used in a later step for alerting.
  • The generate_readings() function loops 24 times, creating a dictionary for each reading with a timestamp and a temperature value. All 24 dictionaries get stored in a list.
  • Save sensor_simulator.py. The function is defined but nothing calls it yet. The next section adds the code that runs it and displays output.

Seeing red underlines in VS Code?

If VS Code shows a red underline under datetime or random, make sure Python is selected as the interpreter. Click the Python version in the bottom-left status bar and select the Python 3 you confirmed in Step 1.

help me fix red underlines for Python imports in VS Code

Print the readings to your terminal

The function generates data, but you need to call it and display the results to see anything.

  • Add the following code at the bottom of sensor_simulator.py, below the function:
# --- Main Program ---
readings = generate_readings(NUM_READINGS, BASE_TEMP, VARIATION)

for reading in readings:
    time_str = reading["timestamp"].strftime("%H:%M")
    temp = reading["temperature"]
    print(f"  {time_str}  |  {temp:>5.1f}C")

What does this code do?

  • generate_readings(NUM_READINGS, BASE_TEMP, VARIATION) calls your function with the configuration values and stores the returned list in readings.
  • The for loop walks through each reading dictionary, pulls out the timestamp and temperature, and prints them in a formatted line.
  • strftime("%H:%M") formats the timestamp as hours and minutes (e.g. 00:00, 01:00).
  • The f"{temp:>5.1f}C" part right-aligns the temperature to 5 characters with 1 decimal place, keeping the output tidy.
  • Save sensor_simulator.py.

Before you run this, how many lines of output do you expect to see? What will each line look like?

  • Run the script in your VS Code terminal:
python sensor_simulator.py

You should see 24 lines of timestamped temperature readings, each looking something like this:

  00:00  |  23.4C
  01:00  |  19.7C
  02:00  |  26.1C
  ...

Your exact numbers will differ because they are randomly generated each time you run it.

Not seeing any output?

  • Make sure you saved the file before running the command. Unsaved changes are not picked up by Python.
  • Check that your main program code is outside and below the function (not indented inside it). If it is indented, Python treats it as part of the function and never runs it.
  • If you see a NameError, make sure the configuration variables (NUM_READINGS, BASE_TEMP, VARIATION) are defined above the function, not inside it.

help me debug my sensor simulator script

✔️ Awesome, I've got everything!

Great. Double check you've saved your file, then move on.

ⓧ I'd like to double check the full code

Your sensor_simulator.py should have these sections in order. Scroll up and confirm each one is present:

  • The two import lines at the top (import random and from datetime import datetime, timedelta).
  • The configuration section with 5 constants: NUM_READINGS, BASE_TEMP, VARIATION, MIN_SAFE, MAX_SAFE.
  • The generate_readings() function that loops, creates dictionaries, and returns a list.
  • The main program section that calls the function and loops through results with a print() statement.

You have 24 lines of data scrolling past. They are just raw numbers. It is hard to tell at a glance if anything is wrong or what the overall pattern is.

Next up, you'll crunch those numbers into statistics that reveal the bigger picture. But as you'll discover, even statistics can hide dangerous spikes.

Analyze the Data

You now have 24 temperature readings scrolling past your terminal. That is data. But raw numbers by themselves do not help you make decisions.

In this step, you will write a function that crunches those readings into useful statistics: the minimum, maximum, and average temperature. These numbers give you the overall picture at a glance.

In this step, get ready to:
  • Write a calculate_stats() function that extracts statistics from your readings.
  • Print a statistics summary below the raw data.
  • Discover a critical gap in relying on averages alone.
Write the calculate_stats() function

A list comprehension lets you pull a single value out of every dictionary in a list in one line. You will use one to extract all the temperature numbers, then feed them to Python's built-in min(), max(), and sum() functions.

  • In sensor_simulator.py, find the blank line between the end of your generate_readings() function and the # --- Main Program --- comment.
  • Add the following function in that space:
def calculate_stats(readings):
    """Calculate min, max, and average temperature."""
    temps = [r["temperature"] for r in readings]
    stats = {
        "min": min(temps),
        "max": max(temps),
        "average": round(sum(temps) / len(temps), 1),
        "count": len(temps)
    }
    return stats

What does this code do?

  • temps = [r["temperature"] for r in readings] is a list comprehension. It loops through every reading dictionary and pulls out just the temperature number, creating a flat list like [22.3, 19.1, 27.8, ...].
  • min(temps) and max(temps) find the lowest and highest values in the list.
  • sum(temps) / len(temps) calculates the average by dividing the total by the count. round(..., 1) keeps it to one decimal place.
  • The function returns a dictionary so you can access each stat by name later (e.g. stats["min"]).
  • Save sensor_simulator.py.
Call the function and print the statistics

The function exists but nothing calls it yet. You need to add code to the main program block that calls calculate_stats() and prints the results in a readable format.

  • Scroll down to the bottom of your file, below the for loop that prints readings.
  • Add the following code at the very end of the file:
stats = calculate_stats(readings)
print()
print("--- Statistics ---")
print(f"  Readings taken: {stats['count']}")
print(f"  Minimum temp:   {stats['min']}C")
print(f"  Maximum temp:   {stats['max']}C")
print(f"  Average temp:   {stats['average']}C")

What does this code do?

  • stats = calculate_stats(readings) calls your new function, passing in the readings list. The returned dictionary gets stored in stats.
  • Each print(f"...") line uses an f-string to plug dictionary values into a formatted label. The stats['count'] syntax accesses a value by its key name.
  • print() with no arguments prints a blank line, visually separating the readings from the statistics.
  • Save sensor_simulator.py.

Seeing a NameError or KeyError?

  • If you see NameError: name 'calculate_stats' is not defined, make sure your new function is placed ABOVE the # --- Main Program --- section. Python reads top to bottom.
  • If you see a KeyError, check that your dictionary keys in the print statements match the keys in the function exactly: min, max, average, count.

Help me debug my calculate_stats function

Run the script and observe the gap

Before you run this, look at your raw readings from the last step. Some of those numbers were quite high or quite low. Do you think the average will reflect those extreme values, or hide them?

  • Run the script by entering this command in your terminal:
python sensor_simulator.py

You should see 24 lines of timestamped readings, followed by a statistics summary showing the count, minimum, maximum, and average temperature.

Now look closely. The average probably sits around 22C, which sounds perfectly safe. But scroll up through the individual readings. You will likely spot numbers above 30C or below 18C mixed in.

Why do averages hide dangerous readings?

An average smooths everything together. One reading at 14C and another at 30C average out to a comfortable 22C. The average looks fine, but both individual moments were dangerous.

In real sensor monitoring, you need something that catches the specific dangerous moments, not just the trend. That is exactly what you will build next.

✔️ Awesome, I've got everything!

Great. Double check you have saved your file before moving on.

ⓧ I'd like to double check the full code

Your file has grown past a quick glance, so scroll through and confirm each section is present in this order:

  • The import lines and configuration variables at the top (NUM_READINGS, BASE_TEMP, etc.).
  • The generate_readings() function (from the previous step).
  • The calculate_stats() function you just added.
  • The # --- Main Program --- section: calls generate_readings(), the for loop printing readings, then calls calculate_stats() and prints the statistics.

You have statistics, but they just proved they are not enough. An average of 22C sounds comfortable while individual readings silently spike to dangerous levels. Next, you will build an alert system that catches the exact moments when a reading goes out of safe range.

Build the Alert System

In the last step, you saw something important. The average temperature might look perfectly safe, but individual readings could be spiking dangerously high or dropping dangerously low.

In real monitoring, you need to catch the exact moment a reading crosses a dangerous boundary. This step adds threshold-based alerting to your system. It is the same pattern used in industrial sensor monitoring and IoT systems.

In this step, get ready to:
  • Write a function that identifies out-of-range temperature readings.
  • Print alerts with timestamps after the statistics section.
  • Verify the alert system catches dangerous readings that statistics miss.
Write the check_alerts() function

The alert system needs to loop through every reading, compare it against a safe temperature range, and build a list of any readings that crossed the boundary.

  • In sensor_simulator.py, find the end of the calculate_stats() function (the return stats line).
  • Add the following function below it:
def check_alerts(readings, min_safe, max_safe):
    """Find readings that fall outside the safe range."""
    alerts = []
    for reading in readings:
        temp = reading["temperature"]
        if temp < min_safe:
            alerts.append({
                "timestamp": reading["timestamp"],
                "temperature": temp,
                "type": "LOW",
                "message": f"Temperature dropped to {temp}C (below {min_safe}C)"
            })
        elif temp > max_safe:
            alerts.append({
                "timestamp": reading["timestamp"],
                "temperature": temp,
                "type": "HIGH",
                "message": f"Temperature spiked to {temp}C (above {max_safe}C)"
            })
    return alerts

What does this code do?

  • check_alerts() takes the readings list plus a minimum and maximum safe temperature as parameters.
  • It loops through every reading and checks whether the temperature is below min_safe or above max_safe.
  • Each out-of-range reading becomes a dictionary with the timestamp, temperature, type ("HIGH" or "LOW"), and a human-readable message.
  • The function returns the full list of alerts. If nothing was out of range, it returns an empty list.
  • Save sensor_simulator.py.

The function is defined but nothing calls it yet. The next chunk wires it into the main program.

Call the function and print alerts
  • Scroll down to the bottom of sensor_simulator.py.
  • Find this line at the end of your main program:
print(f"  Average temp:   {stats['average']}C")
  • Add the following code below that line:
# Check for dangerous readings
alerts = check_alerts(readings, MIN_SAFE, MAX_SAFE)
print()
print("--- Alerts ---")
if alerts:
    print(f"  {len(alerts)} alert(s) triggered!")
    print()
    for alert in alerts:
        time_str = alert["timestamp"].strftime("%H:%M")
        print(f"  [{alert['type']}] {time_str} - {alert['message']}")
else:
    print("  No alerts. All readings within safe range.")

What does this code do?

  • It calls check_alerts() with the configuration constants MIN_SAFE and MAX_SAFE you defined at the top of the file.
  • The if alerts: conditional checks whether the list is empty. In Python, an empty list evaluates to False.
  • If alerts exist, it prints each one with the timestamp and a descriptive message. If not, it prints a reassuring "No alerts" line.
  • Save sensor_simulator.py.

Before you run this, think about what you expect to see. Will there be alerts, or will all 24 readings land safely between 18.0C and 30.0C?

  • Run the script in your terminal:
python sensor_simulator.py

You should see the raw readings and statistics as before, followed by a new "--- Alerts ---" section. If any readings fell below 18.0C or above 30.0C, you will see specific alerts with timestamps telling you exactly when and how far out of range they went.

Not seeing any alerts?

Since the data is random, some runs produce all-safe readings. Run the script a few more times. If you still do not see alerts, temporarily change MIN_SAFE to 20.0 and MAX_SAFE to 25.0 at the top of the file. This narrows the safe range and forces alerts to appear so you can verify your logic works.

Remember to change them back to 18.0 and 30.0 once you have confirmed the alerts display correctly.

Seeing a NameError or TypeError?

  • If you see NameError: name 'check_alerts' is not defined, make sure the function is defined above the main program section (above the # --- Main Program --- comment). Python reads from top to bottom.
  • If you see a TypeError, check that you are passing three arguments: check_alerts(readings, MIN_SAFE, MAX_SAFE).
  • Make sure the indentation of your function body is consistent. Every line inside the function should be indented by four spaces.

Help me debug my check_alerts() function

✔️ Awesome, I've got everything!

Great. Double check you have saved your file.

ⓧ I'd like to double check the full code

Your file is now longer than 30 lines, so instead of pasting the whole thing here, confirm you have these sections in order:

  • The imports (random, datetime, timedelta) at the top.
  • The configuration constants (NUM_READINGS, BASE_TEMP, VARIATION, MIN_SAFE, MAX_SAFE).
  • The generate_readings() function.
  • The calculate_stats() function.
  • The check_alerts() function you just added.
  • The main program section that generates readings, prints them, prints stats, then calls check_alerts() and prints the alerts section.

Why threshold alerts instead of just statistics?

An average of 22.5C looks perfectly safe. But if one reading hit 35C at 2am and another dropped to 14C at 4am, you have a serious problem that the average completely hides.

Threshold-based alerting solves this by checking every individual reading against a boundary. This is the exact same pattern used in real industrial monitoring, server health checks, and the sensor projects you will build on your Pico 2.

Your monitoring system now catches dangerous moments that averages hide. Next up, you will pull everything together into a single professional report with clear formatting and visual markers.

Generate a Formatted Report

Your script now generates data, calculates statistics, and checks alerts. But all that output is scattered across your terminal in separate chunks.

A real monitoring system produces a single clean report that a human can read at a glance. In this step, you will wrap everything into one professional formatted output.

In this step, get ready to:
  • Write a report function that formats all data into a single professional output.
  • Replace scattered print statements with one clean function call.
  • Run the finished monitoring system and see a complete formatted report.
Write the report header and readings table

The generate_report() function takes all three pieces of processed data (readings, stats, and alerts) and prints them as one cohesive report. You will build it in two chunks, starting with the header and the readings table.

  • In sensor_simulator.py, find the check_alerts() function. Below its closing return alerts line, add the first part of your new function by pasting this code:
def generate_report(readings, stats, alerts):
    """Generate a formatted monitoring report."""
    # Print report header with decorative borders
    print("=" * 50)
    print("   TEMPERATURE MONITORING REPORT")
    print("=" * 50)
    print()

    # Print each reading with status markers for dangerous values
    print("--- Sensor Readings ---")
    for reading in readings:
        time_str = reading["timestamp"].strftime("%H:%M")
        temp = reading["temperature"]
        status = ""
        if temp < MIN_SAFE:
            status = " [!!! LOW]"
        elif temp > MAX_SAFE:
            status = " [!!! HIGH]"
        print(f"  {time_str}  |  {temp:>5.1f}C{status}")

What does this code do?

The function starts by printing a bordered header so the report is visually distinct from any other terminal output.

The readings loop checks each temperature against MIN_SAFE and MAX_SAFE. If a reading is out of range, it adds a visible marker like [!!! HIGH] so dangerous readings jump out immediately.

  • Save sensor_simulator.py.
Complete the report and replace the main program

The report needs statistics, alerts, and a footer showing the safe range. Then you will swap out the old scattered prints for a single clean function call.

  • Directly below the last line you just pasted (print(f" {time_str} | {temp:>5.1f}C{status}")), add the rest of the function body:
    # Print statistics summary
    print()
    print("--- Statistics ---")
    print(f"  Readings taken: {stats['count']}")
    print(f"  Minimum temp:   {stats['min']}C")
    print(f"  Maximum temp:   {stats['max']}C")
    print(f"  Average temp:   {stats['average']}C")

    # Print alerts or an all-clear message
    print()
    print("--- Alerts ---")
    if alerts:
        print(f"  {len(alerts)} alert(s) triggered!")
        print()
        for alert in alerts:
            time_str = alert["timestamp"].strftime("%H:%M")
            print(f"  [{alert['type']}] {time_str} - {alert['message']}")
    else:
        print("  No alerts. All readings within safe range.")

    # Print footer with the safe range for reference
    print()
    print("=" * 50)
    print(f"  Safe range: {MIN_SAFE}C - {MAX_SAFE}C")
    print("=" * 50)

What does this section do?

The statistics section prints the same summary you built in Step 3, but now inside the report structure instead of floating on its own.

The alerts section uses an if/else to show either the list of triggered alerts or a reassuring "all clear" message. The footer closes the report with the safe range values so readers know what thresholds were used.

Now your function is complete. The old scattered print statements in the main program are no longer needed.

  • Find the # --- Main Program --- section at the bottom of the file. Select everything from that comment down to the end of the file and delete it.
  • Replace it with this clean main program:
# --- Main Program ---
readings = generate_readings(NUM_READINGS, BASE_TEMP, VARIATION)
stats = calculate_stats(readings)
alerts = check_alerts(readings, MIN_SAFE, MAX_SAFE)
generate_report(readings, stats, alerts)

What changed in the main program?

The main program went from ~20 lines of scattered prints down to 4 clean lines. Each line does one job: generate data, calculate stats, check alerts, display the report. All the formatting logic lives inside generate_report() where it belongs.

  • Save sensor_simulator.py.
Run the complete monitoring system

Before you run this, think about what you expect the output to look like now. Will it still be scattered sections, or something different?

  • Run the script by entering this command in the terminal:
python sensor_simulator.py

You now see a single professional monitoring report. It has a bordered header, a readings table with [!!! HIGH] or [!!! LOW] markers on dangerous values, statistics, alerts, and a footer showing the safe range.

Seeing an error or no output?

  • If you see IndentationError, make sure the entire function body (everything after the def line) is indented with exactly 4 spaces.
  • If you see the old scattered output followed by the report, you still have the old print statements above the new main program. Delete everything between the end of generate_report() and the new # --- Main Program --- comment.
  • If you see NameError: name 'generate_report' is not defined, make sure the function definition sits above the main program section (Python reads top to bottom).

Help me debug my generate_report() function

✔️ Awesome, I've got everything!

Great. Double check you have saved sensor_simulator.py and your report prints cleanly with no leftover scattered output.

ⓧ I'd like to double check the full code

Your sensor_simulator.py file should contain these sections in order. Scroll through your file and confirm each one is present:

  • Imports (random, datetime, timedelta) and configuration constants at the top.
  • generate_readings() function that creates 24 timestamped readings.
  • calculate_stats() function that returns min, max, average, and count.
  • check_alerts() function that finds out-of-range readings.
  • generate_report() function that prints the formatted report with header, readings, stats, alerts, and footer.
  • A 4-line main program that calls generate_readings(), calculate_stats(), check_alerts(), and generate_report(). No other print statements remain.

You now have a complete temperature monitoring system in a single script. One command produces a professional report with raw data, statistical analysis, threshold alerts, and clear visual markers. This is the same data pipeline pattern used in real sensor monitoring on devices like the Pico 2 and Arduino.

Secret mission

Add Humidity and CSV Export

Real environmental monitoring systems track multiple sensors simultaneously and export data for analysis in other tools. Can you extend your monitoring system to track humidity alongside temperature, then export everything to a CSV file you can open in Excel?

Clean Up Your Resources

Clean Up Your Resources

Decide whether to keep your resources running, pause them to come back later, or delete them entirely. This project runs entirely locally, so there are no ongoing costs.

Resources you used:

  • The temp-monitor folder on your Desktop containing sensor_simulator.py and sensor_report.csv.

Keep everything running

No action needed. Choose this if you plan to keep building on this project or want to reference it later.

  • Your temp-monitor folder stays on your Desktop. You can re-run the script anytime.
  • Each time you run sensor_simulator.py, it overwrites sensor_report.csv with fresh data.

Pause - I'll come back to this later

Since this project is entirely local files, there is nothing to pause. Your files sit on disk and consume no memory or processing power when the script is not running.

  • Close the temp-monitor folder in VS Code if you want to free up a workspace tab.
  • Everything will be exactly as you left it when you come back.

Delete - I don't want to use this again

Remove all project files from your machine.

  • Close the temp-monitor folder in VS Code.
  • Delete the folder by running the command below for your operating system.

macOS

rm -rf ~/Desktop/temp-monitor

Windows

rmdir /s /q %USERPROFILE%\Desktop\temp-monitor
  • Confirm the folder is gone by checking your Desktop.

Nice Work!

Nice Work!

Nice work! You just built a complete temperature monitoring system from scratch in Python, combining every core fundamental into a single working program.

Here's what you learned:

  • Built a data generation pipeline that simulates real sensor behavior using variables, loops, lists, and dictionaries.
  • Built a threshold-based alert system using conditionals and functions to catch dangerous readings the same way industrial IoT systems do.
  • Generated a professional formatted report using f-strings and imports that pulls raw data into a clean, readable output.
  • Secret Mission: Added multi-sensor humidity monitoring and CSV file export.

Ready to quiz yourself?