================================================================================
  Question 4: CoAP Weather Monitoring & Decision Tree Classification [30 Marks]
================================================================================

FULL QUESTION:
CAE 2 IoT Practical Exam - Section B (Answer either Q3 or Q4):
Q4. CoAP-Based Weather Monitoring and Decision Tree Classification [30 Marks]
Develop a CoAP-based weather monitoring and classification system for Kochi.
Input API Link: https://api.open-meteo.com/v1/forecast?latitude=9.93&longitude=76.27&current=temperature_2m,relative_humidity_2m,precipitation,cloud_cover,wind_speed_10m,surface_pressure
Historical Dataset API: https://archive-api.open-meteo.com/v1/archive?latitude=9.93&longitude=76.27&start_date=2023-01-01&end_date=2025-12-31&hourly=temperature_2m,relative_humidity_2m,precipitation,cloud_cover,wind_speed_10m,surface_pressure,weather_code
Target weather_class: 0,1 -> Clear; 2,3 -> Cloudy; 51,53,55 -> Drizzle; 61,63,65 -> Rain.
The system should:
- Develop CoAP Server and CoAP Client. [6 Marks]
- Retrieve live weather data of Kochi via CoAP Server. [6 Marks]
- Transfer data to CoAP Client using CoAP GET request. [5 Marks]
- Develop dashboard to display Temperature, Humidity, Precipitation, Cloud Cover, Wind Speed, Surface Pressure. [5 Marks]
- Train Decision Tree model on historical weather dataset to classify current weather into Clear, Cloudy, Drizzle, or Rain. [5 Marks]
- Display predicted weather classification on dashboard. [3 Marks]
Flow: Open-Meteo Weather API -> CoAP Server -> CoAP Client -> Dashboard -> Decision Tree Classification.

SHORT QUESTION:
CoAP Weather Server & Client with Decision Tree Classification and Dashboard [30 Marks].

================================================================================
README & SETUP INSTRUCTIONS:
================================================================================
# Question 4: CoAP-Based Weather Monitoring and Decision Tree Classification

## Problem Statement [30 Marks]
Develop a CoAP-based weather monitoring and classification system for Kochi.

- **Input API Link**:
  `https://api.open-meteo.com/v1/forecast?latitude=9.93&longitude=76.27&current=temperature_2m,relative_humidity_2m,precipitation,cloud_cover,wind_speed_10m,surface_pressure`
- **Weather Parameters**:
  Temperature, Relative Humidity, Precipitation, Cloud Cover, Wind Speed, Surface Pressure
- **Historical Dataset API**:
  `https://archive-api.open-meteo.com/v1/archive?latitude=9.93&longitude=76.27&start_date=2023-01-01&end_date=2025-12-31&hourly=temperature_2m,relative_humidity_2m,precipitation,cloud_cover,wind_speed_10m,surface_pressure,weather_code`
- **Weather Class Mapping**:
  | weather_code | weather_class |
  | :--- | :--- |
  | `0, 1` | **Clear** |
  | `2, 3` | **Cloudy** |
  | `51, 53, 55` | **Drizzle** |
  | `61, 63, 65` | **Rain** |
- **Model**: Decision Tree Classifier
  - Input Features: `temperature_2m`, `relative_humidity_2m`, `precipitation`, `cloud_cover`, `wind_speed_10m`, `surface_pressure`
  - Target: `weather_class`

---

## Architectural Flow
```text
Open-Meteo Weather API ──> CoAP Server (UDP 5683) ──> CoAP Client ──> Dashboard ──> Decision Tree Classification
```

---

## Files in this Folder
- `server.py`: Asynchronous CoAP Server listening on port 5683 (`/weather`), retrieving live Kochi weather telemetry from Open-Meteo API.
- `client.py`: Standalone CLI CoAP Client that queries the CoAP server, trains the Decision Tree on historical observations, and displays predictions in the terminal.
- `app.py`: Interactive Streamlit dashboard and CoAP Client with visualizations, metric cards, and interactive manual parameter tuning.
- `historical_weather_kochi.csv`: 26,304 historical hourly weather observations for Kochi with mapped weather classes.

---

## How to Run

### Step 1: Install Dependencies
```bash
pip install pandas scikit-learn streamlit aiocoap requests matplotlib
```

### Step 2: Start the CoAP Server
Open a terminal in the `Q4` directory:
```bash
python server.py
```
*Expected Server Output:*
```text
============================================================
  Kochi CoAP Weather Server Active on Port 5683
============================================================
Endpoint: coap://127.0.0.1:5683/weather
Waiting for CoAP Client GET requests...
```

### Step 3: Run the CLI Client OR the Streamlit Dashboard

#### Option A: Run CLI Client
Open a second terminal in the `Q4` directory:
```bash
python client.py
```

#### Option B: Run Streamlit Dashboard
```bash
streamlit run app.py
```
Open your browser at `http://localhost:8501`. Click **"Fetch Live Weather via CoAP"** to initiate a CoAP GET transaction and view live predictions and model metrics!

================================================================================
SOURCE CODE FILES:
================================================================================

1. server.py
--------------------------------------------------------------------------------
import asyncio
import json
import requests
from aiocoap import Context, Message, resource

LIVE_WEATHER_URL = (
    "https://api.open-meteo.com/v1/forecast?"
    "latitude=9.93&longitude=76.27&"
    "current=temperature_2m,relative_humidity_2m,precipitation,cloud_cover,wind_speed_10m,surface_pressure"
)

class WeatherResource(resource.Resource):

    def fetch_live_api(self):
        response = requests.get(LIVE_WEATHER_URL, timeout=10)
        response.raise_for_status()
        return response.json().get("current", {})

    async def render_get(self, request):
        print(f"\n[CoAP Server] Received GET request from {request.remote}")
        try:

            current = await asyncio.to_thread(self.fetch_live_api)

            payload = {
                "status": "success",
                "city": "Kochi",
                "latitude": 9.93,
                "longitude": 76.27,
                "time": current.get("time"),
                "temperature_2m": current.get("temperature_2m"),
                "relative_humidity_2m": current.get("relative_humidity_2m"),
                "precipitation": current.get("precipitation"),
                "cloud_cover": current.get("cloud_cover"),
                "wind_speed_10m": current.get("wind_speed_10m"),
                "surface_pressure": current.get("surface_pressure"),
            }

            print("[CoAP Server] Serving live weather parameters:")
            print(f"  Temperature      : {payload['temperature_2m']} °C")
            print(f"  Relative Humidity: {payload['relative_humidity_2m']} %")
            print(f"  Precipitation    : {payload['precipitation']} mm")
            print(f"  Cloud Cover      : {payload['cloud_cover']} %")
            print(f"  Wind Speed       : {payload['wind_speed_10m']} km/h")
            print(f"  Surface Pressure : {payload['surface_pressure']} hPa")

            encoded = json.dumps(payload).encode("utf-8")
            return Message(payload=encoded)

        except Exception as e:
            error_msg = json.dumps({"status": "error", "error": str(e)}).encode("utf-8")
            print(f"[CoAP Server] Error fetching live weather API: {e}")
            return Message(payload=error_msg)

async def main():
    root = resource.Site()
    root.add_resource(["weather"], WeatherResource())

    await Context.create_server_context(root, bind=("127.0.0.1", 5683))
    print("=" * 60)
    print("  Kochi CoAP Weather Server Active on Port 5683")
    print("=" * 60)
    print("Endpoint: coap://127.0.0.1:5683/weather")
    print("Waiting for CoAP Client GET requests (Press Ctrl+C to stop)...")
    await asyncio.get_running_loop().create_future()

if __name__ == "__main__":
    try:
        asyncio.run(main())
    except KeyboardInterrupt:
        print("\n[CoAP Server] Stopped by user.")
--------------------------------------------------------------------------------

2. client.py
--------------------------------------------------------------------------------
import asyncio
import json
import os
import requests
import pandas as pd
from aiocoap import Context, Message, GET
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import accuracy_score, classification_report

FEATURES = [
    "temperature_2m",
    "relative_humidity_2m",
    "precipitation",
    "cloud_cover",
    "wind_speed_10m",
    "surface_pressure"
]
TARGET = "weather_class"
SERVER_URI = "coap://127.0.0.1:5683/weather"
HISTORICAL_CSV = "historical_weather_kochi.csv"
HISTORICAL_API = (
    "https://archive-api.open-meteo.com/v1/archive?"
    "latitude=9.93&longitude=76.27&start_date=2023-01-01&end_date=2025-12-31&"
    "hourly=temperature_2m,relative_humidity_2m,precipitation,cloud_cover,wind_speed_10m,surface_pressure,weather_code"
)

WEATHER_CODE_MAPPING = {
    0: "Clear", 1: "Clear",
    2: "Cloudy", 3: "Cloudy",
    51: "Drizzle", 53: "Drizzle", 55: "Drizzle",
    61: "Rain", 63: "Rain", 65: "Rain"
}

async def fetch_weather_coap(uri=SERVER_URI):
    protocol = await Context.create_client_context()
    try:
        request = Message(code=GET, uri=uri)
        response = await protocol.request(request).response
        return json.loads(response.payload.decode("utf-8"))
    finally:
        await protocol.shutdown()

def load_or_fetch_dataset():
    if os.path.exists(HISTORICAL_CSV):
        print(f"[1] Loading historical weather data from '{HISTORICAL_CSV}'...")
        df = pd.read_csv(HISTORICAL_CSV)
    else:
        print("[1] Local CSV not found. Fetching historical weather dataset from Open-Meteo Archive API...")
        r = requests.get(HISTORICAL_API, timeout=30)
        r.raise_for_status()
        hourly = r.json()["hourly"]
        df = pd.DataFrame(hourly)
        df["weather_class"] = df["weather_code"].map(WEATHER_CODE_MAPPING)
        df.to_csv(HISTORICAL_CSV, index=False)
        print(f"    Saved {len(df)} records to '{HISTORICAL_CSV}'.")

    if "weather_class" not in df.columns:
        df["weather_class"] = df["weather_code"].map(WEATHER_CODE_MAPPING)

    return df

def train_decision_tree():
    df = load_or_fetch_dataset()
    data = df[FEATURES + [TARGET]].dropna()
    print(f"    Dataset loaded with {len(data):,} valid historical observations.")

    X = data[FEATURES]
    y = data[TARGET]

    X_train, X_test, y_train, y_test = train_test_split(
        X, y, test_size=0.20, random_state=42, stratify=y
    )

    print("[2] Training Decision Tree Classifier...")
    model = DecisionTreeClassifier(max_depth=10, random_state=42)
    model.fit(X_train, y_train)

    y_pred = model.predict(X_test)
    acc = accuracy_score(y_test, y_pred)
    print(f"    Model Accuracy: {acc * 100:.2f}%\n")
    print("Classification Summary:")
    print(classification_report(y_test, y_pred))

    return model, acc

def main():
    print("=" * 70)
    print("  Kochi CoAP Weather Monitor & Decision Tree Classification")
    print("=" * 70)

    print(f"\n[Step 1] Connecting to CoAP Server at {SERVER_URI}...")
    try:
        coap_data = asyncio.run(fetch_weather_coap())
        if coap_data.get("status") == "error":
            print(f"[Error from Server]: {coap_data.get('error')}")
            return
    except Exception as e:
        print(f"[ERROR] Could not connect to CoAP Server on 127.0.0.1:5683: {e}")
        print("Please ensure 'python server.py' is running in another terminal.")
        return

    print("\n[Step 2] Live Weather Telemetry Received via CoAP:")
    print(f"  Location          : {coap_data.get('city')} (Lat: {coap_data.get('latitude')}, Lon: {coap_data.get('longitude')})")
    print(f"  Timestamp         : {coap_data.get('time')}")
    print(f"  Temperature       : {coap_data.get('temperature_2m')} °C")
    print(f"  Relative Humidity : {coap_data.get('relative_humidity_2m')} %")
    print(f"  Precipitation     : {coap_data.get('precipitation')} mm")
    print(f"  Cloud Cover       : {coap_data.get('cloud_cover')} %")
    print(f"  Wind Speed        : {coap_data.get('wind_speed_10m')} km/h")
    print(f"  Surface Pressure  : {coap_data.get('surface_pressure')} hPa")

    model, _ = train_decision_tree()

    input_sample = pd.DataFrame([{
        "temperature_2m": float(coap_data["temperature_2m"]),
        "relative_humidity_2m": float(coap_data["relative_humidity_2m"]),
        "precipitation": float(coap_data["precipitation"]),
        "cloud_cover": float(coap_data["cloud_cover"]),
        "wind_speed_10m": float(coap_data["wind_speed_10m"]),
        "surface_pressure": float(coap_data["surface_pressure"])
    }])

    predicted_class = model.predict(input_sample)[0]
    probabilities = model.predict_proba(input_sample)[0]

    print("=" * 70)
    print(f"  PREDICTED WEATHER CLASS: >>> {predicted_class.upper()} <<<")
    print("=" * 70)
    print("Class Probabilities:")
    for cls, prob in zip(model.classes_, probabilities):
        print(f"  {cls:10s}: {prob * 100:6.2f}%")

if __name__ == "__main__":
    main()
--------------------------------------------------------------------------------

3. app.py
--------------------------------------------------------------------------------
import asyncio
import json
import streamlit as st
import pandas as pd
from aiocoap import Context, Message, GET
from sklearn.tree import DecisionTreeClassifier

st.title("Kochi Weather Monitoring & Decision Tree")

FEATURES = ["temperature_2m", "relative_humidity_2m", "precipitation", "cloud_cover", "wind_speed_10m", "surface_pressure"]

@st.cache_resource
def get_model():
    df = pd.read_csv("historical_weather_kochi.csv")
    mapping = {0: "Clear", 1: "Clear", 2: "Cloudy", 3: "Cloudy", 51: "Drizzle", 53: "Drizzle", 55: "Drizzle", 61: "Rain", 63: "Rain", 65: "Rain"}
    df["weather_class"] = df["weather_code"].map(mapping)
    data = df[FEATURES + ["weather_class"]].dropna()
    X = data[FEATURES]
    y = data["weather_class"]
    model = DecisionTreeClassifier(max_depth=10, random_state=42)
    model.fit(X, y)
    return model

model = get_model()

async def fetch_coap():
    ctx = await Context.create_client_context()
    req = Message(code=GET, uri="coap://127.0.0.1:5683/weather")
    res = await ctx.request(req).response
    await ctx.shutdown()
    return json.loads(res.payload.decode("utf-8"))

if st.button("Fetch Live Weather via CoAP"):
    live_data = asyncio.run(fetch_coap())
    st.subheader("Retrieved Weather Parameters:")
    st.write(live_data)

    sample = pd.DataFrame([[live_data[f] for f in FEATURES]], columns=FEATURES)
    prediction = model.predict(sample)[0]
    st.success(f"Predicted Weather Category: {prediction}")
--------------------------------------------------------------------------------
