Objective: Build a containerized web application that displays real-time temperature data from multiple sources in an interactive dashboard.
What You'll Build:
Skills Applied: Microcontroller programming, web development, API integration, containerization, and real-time data visualization
Sensors You'll Use:
Reference Materials:
Your Task: Write Arduino code that reads both sensors and sends temperature data via Serial in a structured format (e.g., JSON or CSV)
💡 Tip: Use a consistent format like: DHT:25.5,MF52:25.3 to make parsing easier in your web app
Recommended Serial Output Format:
Your Arduino should continuously output temperature readings in a parseable format. Here's an example:
💡 Pro Tips:
void loop() {
float dht_temp = dht.readTemperature();
float mf52_temp = readMF52Temperature();
// JSON format (recommended)
Serial.print("{\"dht\":");
Serial.print(dht_temp);
Serial.print(",\"mf52\":");
Serial.print(mf52_temp);
Serial.println("}");
delay(2000);
}
Technology Stack Options:
Application Components:
Using PySerial Library:
Install with: pip install pyserial
💡 Important Tips:
/dev/ttyUSB0, /dev/ttyACM0 (Linux/Mac) or COM3, COM4 (Windows)import serial
import serial.tools.list_ports
import json
def find_arduino_port():
"""Auto-detect Arduino port"""
ports = serial.tools.list_ports.comports()
for port in ports:
# Look for Arduino in description
if 'Arduino' in port.description or \
'USB' in port.description:
return port.device
return None
# Auto-detect or use environment variable
port = find_arduino_port() or '/dev/ttyUSB0'
ser = serial.Serial(port, 9600, timeout=1)
def read_arduino_data():
if ser.in_waiting > 0:
line = ser.readline().decode('utf-8').strip()
data = json.loads(line)
return {'dht': data['dht'], 'mf52': data['mf52']}
return None
Reference Your Previous Learning:
Refer back to your weather API slides for details on API keys, endpoints, and data parsing.
Your Task:
💡 Best Practices:
Three Panel Layout:
Recommended Charting Libraries:
💡 Design Tips:
Option 1: WebSockets (Recommended)
Two-way communication for instant updates
💡 Implementation Tips:
// Frontend WebSocket connection
const ws = new WebSocket('ws://localhost:8000');
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
// Update DHT11 chart
updateChart(dhtChart, data.dht);
// Update MF52A chart
updateChart(mf52Chart, data.mf52);
// Update outdoor temp if available
if (data.outdoor) {
updateChart(outdoorChart, data.outdoor);
}
};
Key Considerations:
💡 Performance Tip: Use a sliding window approach - remove old data as new data arrives
function updateChart(chart, newValue) {
const now = new Date();
// Add new data point
chart.data.labels.push(now.toLocaleTimeString());
chart.data.datasets[0].data.push(newValue);
// Keep only last 50 points
if (chart.data.labels.length > 50) {
chart.data.labels.shift();
chart.data.datasets[0].data.shift();
}
// Update the chart
chart.update('none'); // 'none' disables animation
}
Organize your project for easy containerization:
💡 Tip: Keep frontend and backend separate but in the same project directory
temperature-monitor/
├── arduino/
│ └── sensor_reader.ino
├── backend/
│ ├── app.py (or server.js)
│ ├── serial_reader.py
│ ├── weather_api.py
│ └── requirements.txt
├── frontend/
│ └── index.html
├── Dockerfile
└── docker-compose.yml
Why Containerize?
Prerequisites:
💡 Development Tip: Always test your app locally before containerizing. Debug Docker issues separately from application bugs!
Basic Dockerfile Structure:
This example is for a Python Flask application
💡 Key Points:
FROM python:3.10-slim
WORKDIR /app
# Install dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy application code
COPY backend/ ./backend/
COPY frontend/ ./frontend/
# Expose port
EXPOSE 8000
# Run the application
CMD ["python", "backend/app.py"]
The Challenge: Docker containers are isolated from your host's hardware. You must explicitly give the container access to the serial port.
Step 1: Find Your Arduino Port
ls /dev/tty* or ls /dev/cu.*Step 2: Add Device Mapping to Docker Command
Use the --device flag to map the serial port:
docker run --device=/dev/ttyUSB0:/dev/ttyUSB0 your-image
💡 Important: The port path inside the container (after the colon) should match what your code expects!
Docker Compose makes running containers easier:
Key Configuration:
devices: Map the serial portprivileged: May be needed for serial access (use cautiously)ports: Expose web application portRun with: docker-compose up
version: '3.8'
services:
temp-monitor:
build: .
ports:
- "8000:8000"
devices:
- "/dev/ttyUSB0:/dev/ttyUSB0"
environment:
- SERIAL_PORT=/dev/ttyUSB0
- WEATHER_API_KEY=${WEATHER_API_KEY}
# Optional: if serial access issues persist
# privileged: true
Common Issues & Solutions:
Problem 1: Permission Denied
sudo usermod -a -G dialout $USERProblem 2: Port Not Found
ls -l /dev/ttyUSB*Problem 3: Port Name Changes
💡 Debug Tip: Test serial communication outside Docker first, then troubleshoot Docker-specific issues
Build Process:
💡 Testing Checklist:
# Build the Docker image
docker build -t temp-monitor .
# Run with serial port mapping
docker run -p 8000:8000 \
--device=/dev/ttyUSB0:/dev/ttyUSB0 \
-e WEATHER_API_KEY=your_key_here \
temp-monitor
# Or use docker-compose
docker-compose up
# Access in browser
# http://localhost:8000
Never hardcode sensitive information!
Create a .env file:
💡 Security Tips:
# .env file (don't commit this!)
WEATHER_API_KEY=abc123xyz789
SERIAL_PORT=/dev/ttyUSB0
BAUD_RATE=9600
# .env.example (commit this)
WEATHER_API_KEY=your_api_key_here
SERIAL_PORT=/dev/ttyUSB0
BAUD_RATE=9600
# Access in Python
import os
api_key = os.getenv('WEATHER_API_KEY')
Before Submission, Verify:
✓ Hardware Layer:
✓ Software Layer:
✓ Containerization:
🚫 Don't Do This:
✅ Do This Instead: Plan your architecture, test incrementally, handle errors gracefully, document as you go!