Project 8: Local Web Dashboard for Sensor Data

Build a lightweight local web UI that serves live sensor data without blocking the acquisition loop.

Quick Reference

Attribute Value
Difficulty Intermediate
Time Estimate 1–2 weekends
Main Programming Language Python (Alternatives: Go, Rust, Node.js)
Alternative Programming Languages Go, Rust, Node.js
Coolness Level Medium
Business Potential Medium
Prerequisites HTTP basics, Linux CLI, sensor loop from P03
Key Topics HTTP server, data caching, inter-process separation, mDNS

1. Learning Objectives

By completing this project, you will:

  1. Build a local HTTP server that serves JSON and HTML.
  2. Decouple sensor acquisition from web requests.
  3. Implement caching and rate limiting for stable performance.
  4. Secure a local web UI with minimal overhead.

2. All Theory Needed (Per-Concept Breakdown)

Concept 1: Lightweight HTTP Services and Data Sharing Patterns

Fundamentals

An embedded web dashboard is a simple HTTP server that returns data in JSON and serves a static page. The challenge is not HTTP itself; it is ensuring that web requests do not block sensor collection or crash the device. To achieve this, you separate the sensor loop from the web server and share data via a cache or IPC mechanism. A lightweight, local UI must be efficient, predictable, and secure enough for a LAN environment. Understanding request/response flow and data caching is the core of this project.

Deep Dive into the concept

HTTP is a request/response protocol over TCP. The browser requests a URL, and the server responds with headers and a body. For embedded dashboards, you typically serve a static HTML page and a /status endpoint that returns JSON. The browser uses JavaScript to fetch JSON periodically and update the UI. This design keeps the server simple: one endpoint for UI, one endpoint for data.

The sensor loop should run on its own schedule, independent of HTTP requests. If a web request triggers a sensor read, then a slow sensor or blocking I/O can make the UI sluggish or even crash the server. The solution is to maintain a cached “latest reading” in memory or in a local file. The acquisition loop updates the cache at a fixed interval, while the web server returns the cached value immediately. This pattern is sometimes called “producer-consumer” or “push-to-cache, pull-from-cache.”

You must consider concurrency. In Python, you can implement the sensor loop in a separate thread or process, or use an async event loop. If you share memory, you need synchronization (a lock or atomic update). A common pattern is to store the latest reading in a global dictionary and update it under a lock; the HTTP handler reads it without blocking. Alternatively, you can write the data to a JSON file and read it from the server, but file I/O adds latency and wear. For a Pi Zero 2 W, a small in-memory cache is simplest and fastest.

Rate limiting is important because browsers can refresh aggressively. A client that polls every 100 ms will waste CPU and network bandwidth. Your server should either enforce a minimum interval or return cache headers that instruct the browser to reuse data. Even in local networks, this matters for battery-powered systems. You should also consider mDNS (e.g., pi-zero2w.local) for discovery. This depends on avahi-daemon; if it is disabled, you need a fallback (IP address or static DNS). Security is minimal but not ignored: if the device is on a shared network, basic auth or a simple token can prevent casual access.

Determinism is also relevant. For reproducible tests, your /status endpoint should accept a “fixed time” environment variable, or the server should allow a simulation mode that serves deterministic data. This makes it possible to write golden-path tests that do not depend on real sensors.

How this fit on projects

This concept is used in §3, §4, and §5.10. It connects to Project 9 (MQTT publishing) and the capstone project.

Definitions & key terms

  • HTTP: Hypertext Transfer Protocol.
  • JSON: Lightweight data interchange format.
  • Cache: Stored data returned without recomputing.
  • mDNS: Local network name resolution.
  • Rate limiting: Restricting request frequency.

Mental model diagram (ASCII)

Sensor Loop -> Cache -> HTTP /status -> Browser UI

How it works (step-by-step, with invariants and failure modes)

  1. Sensor loop updates cache on schedule.
  2. HTTP server serves static UI.
  3. /status returns cached JSON.

Failure modes:

  • Cache missing -> server returns error.
  • Sensor loop blocked -> stale data.
  • Excessive polling -> high CPU.

Minimal concrete example

latest = {"temp": 22.4, "humidity": 45.6}
@app.route('/status')
def status():
    return jsonify(latest)

Common misconceptions

  • “The web server should read the sensor directly.” That blocks and adds jitter.
  • “Local networks are always safe.” Basic protection is still needed.

Check-your-understanding questions

  1. Why should sensor reads be decoupled from HTTP requests?
  2. How does caching improve reliability?
  3. What breaks if mDNS is disabled?

Check-your-understanding answers

  1. It prevents slow sensors from blocking the UI.
  2. It provides fast, consistent responses.
  3. hostname.local discovery fails; you need IP fallback.

Real-world applications

  • Local dashboards for thermostats, routers, and lab sensors.

Where you’ll apply it

References

  • “Computer Networks” — HTTP basics
  • Flask/FastAPI docs

Key insights

Serving data is easy; serving data without blocking the device is the real challenge.

Summary

A local dashboard is a small HTTP server backed by cached sensor data and careful rate limits.

Homework/Exercises to practice the concept

  1. Build a JSON endpoint that returns fixed data.
  2. Add a cache and update it from a background loop.
  3. Test how refresh intervals affect CPU load.

Solutions to the homework/exercises

  1. Use a minimal HTTP server and return JSON.
  2. Update the cache in a thread and read in the handler.
  3. Higher refresh rates increase CPU; choose a balance (1–5 s).

3. Project Specification

3.1 What You Will Build

A local web dashboard with a static UI and a JSON endpoint that serves cached sensor data.

3.2 Functional Requirements

  1. Serve / with a simple HTML page.
  2. Serve /status with JSON sensor data.
  3. Update sensor data on a fixed schedule.
  4. Cache results and avoid blocking HTTP requests.

3.3 Non-Functional Requirements

  • Performance: /status responds < 50 ms.
  • Reliability: No missed sensor updates in 1 hour.
  • Usability: UI readable on mobile.

3.4 Example Usage / Output

$ curl http://pi-zero2w.local/status
{"temp_c":22.4,"humidity":45.6,"uptime":"3h14m"}

3.5 Data Formats / Schemas / Protocols

JSON response:

{"temp_c":22.4,"humidity":45.6,"uptime":"3h14m","last_update":"10:21:05"}

3.6 Edge Cases

  • Sensor data not available.
  • Excessive polling.
  • No mDNS resolution.

3.7 Real World Outcome

A phone on the same network shows live sensor data with stable refresh.

3.7.1 How to Run (Copy/Paste)

python3 dashboard.py --host 0.0.0.0 --port 8080

3.7.2 Golden Path Demo (Deterministic)

export FIXED_TIME="2026-01-01T10:50:00Z"
python3 dashboard.py --simulate

Expected output:

[2026-01-01T10:50:00Z] /status -> 200

3.7.3 Failure Demo (Deterministic)

python3 dashboard.py --simulate --cache-missing

Expected output:

[ERROR] Cache unavailable

Exit code: 81

3.7.4 CLI Exit Codes

  • 0: Success
  • 80: HTTP server failed
  • 81: Cache missing

4. Solution Architecture

4.1 High-Level Design

Sensor Loop -> Cache -> HTTP Server -> Browser UI

4.2 Key Components

| Component | Responsibility | Key Decisions | |—|—|—| | Sensor Loop | Gather data | Interval | | Cache | Store latest | In-memory vs file | | HTTP Server | Serve endpoints | Framework choice | | UI | Display data | Polling interval |

4.3 Data Structures (No Full Code)

latest = {"temp_c": 22.4, "humidity": 45.6, "ts": ts}

4.4 Algorithm Overview

Key Algorithm: Cached Status Response

  1. Update cache on schedule.
  2. Serve cache on request.
  3. Rate-limit polling.

Complexity Analysis:

  • Time: O(1) per request
  • Space: O(1)

5. Implementation Guide

5.1 Development Environment Setup

pip install flask

5.2 Project Structure

project-root/
├── dashboard.py
├── sensor_loop.py
├── static/
│   └── index.html
└── README.md

5.3 The Core Question You’re Answering

“How do you present live device data without destabilizing the device?”

5.4 Concepts You Must Understand First

  1. HTTP request/response basics.
  2. Sensor loop separation and caching.
  3. mDNS discovery and local networking.

5.5 Questions to Guide Your Design

  1. How will you keep data fresh without blocking?
  2. What is a safe polling interval for the UI?

5.6 Thinking Exercise

Draw a data flow diagram from sensor read to browser update.

5.7 The Interview Questions They’ll Ask

  1. Why separate the sensor loop from web requests?
  2. How do you reduce server load on small devices?
  3. How would you secure a local dashboard?

5.8 Hints in Layers

Hint 1: Serve static HTML first.

Hint 2: Add a /status JSON endpoint.

Hint 3: Cache the latest sensor reading.

5.9 Books That Will Help

| Topic | Book | Chapter | |—|—|—| | HTTP basics | Computer Networks | Ch. 2 | | Processes | The Linux Programming Interface | Ch. 6 |

5.10 Implementation Phases

Phase 1: Server bring-up (2 hours)

  • Serve static HTML and JSON endpoint.

Phase 2: Sensor loop (4 hours)

  • Add sensor cache updated on schedule.

Phase 3: UI polishing (3 hours)

  • Add refresh logic and mobile-friendly layout.

5.11 Key Implementation Decisions

| Decision | Options | Recommendation | Rationale | |—|—|—|—| | Framework | Flask / FastAPI / bare socket | Flask | Simple and lightweight | | Cache | Memory / File | Memory | Low latency |


6. Testing Strategy

6.1 Test Categories

| Category | Purpose | Examples | |—|—|—| | Unit Tests | Cache update | JSON correctness | | Integration Tests | HTTP responses | /status 200 | | Edge Case Tests | Missing cache | Exit 81 |

6.2 Critical Test Cases

  1. /status returns JSON every time.
  2. Sensor loop runs even with no web traffic.
  3. Cache missing -> error response.

6.3 Test Data

{"temp_c":22.4,"humidity":45.6}

7. Common Pitfalls & Debugging

7.1 Frequent Mistakes

| Pitfall | Symptom | Solution | |—|—|—| | Sensor reads in handler | Slow responses | Use cache | | Aggressive polling | High CPU | Rate-limit | | No mDNS | Hostname not found | Use IP |

7.2 Debugging Strategies

  • Use curl and ab to test response time.
  • Monitor CPU with top while refreshing.

7.3 Performance Traps

  • Rendering templates on every request can add overhead.

8. Extensions & Challenges

8.1 Beginner Extensions

  • Add uptime and CPU load display.

8.2 Intermediate Extensions

  • Add authentication token for /status.

8.3 Advanced Extensions

  • Add WebSocket push updates.

9. Real-World Connections

9.1 Industry Applications

  • Local dashboards for home automation and industrial sensors.
  • Home Assistant local dashboards.

9.3 Interview Relevance

  • Discussing caching and separation of concerns is common in interviews.

10. Resources

10.1 Essential Reading

  • Flask documentation (or chosen framework docs).

10.2 Video Resources

  • Building lightweight web servers tutorials.

10.3 Tools & Documentation

  • curl and ab for testing.

11. Self-Assessment Checklist

11.1 Understanding

  • I can explain HTTP request/response flow.
  • I can explain why caching is necessary.

11.2 Implementation

  • Dashboard serves data reliably.
  • Sensor loop is independent of web traffic.

11.3 Growth

  • I can explain the architecture in an interview.

12. Submission / Completion Criteria

Minimum Viable Completion:

  • /status endpoint returns JSON.

Full Completion:

  • Live UI with caching and rate limiting.

Excellence (Going Above & Beyond):

  • WebSocket push updates with authentication.