================================================================================
  Question CQ1: CoAP Environmental Monitoring & Linear Regression Prediction [30 Marks]
================================================================================

FULL QUESTION:
CAE 2 IoT Practical Exam - Combined Question:
CQ1. CoAP-Based Environmental Monitoring and Multiple Linear Regression Temperature Prediction [30 Marks]
Develop a CoAP-based environmental forecasting and telemetry system.
Input API / Data Source: Atmospheric sensors transmitting PM2.5, PM10, Atmospheric Pressure (PRES), Dew Point (DEWP), and Wind Speed (WSPM).
Historical Dataset: Beijing Multi-Site Air Quality dataset (PRSA_Data_Aotizhongxin_20130301-20170228.csv).
Target Variable: Ambient Temperature (TEMP in °C).
The system should:
- Develop a CoAP Server and CoAP Client using UDP port 5683. [6 Marks]
- Retrieve live environmental parameters via CoAP Server (from API or sensor feed). [6 Marks]
- Transfer data to CoAP Client using CoAP GET request. [5 Marks]
- Train and evaluate a Multiple Linear Regression model on historical environmental dataset to predict Temperature (TEMP) from PM2.5, PM10, PRES, DEWP, and WSPM. Compute R2, MAE, and RMSE metrics. [5 Marks]
- Develop a Streamlit web dashboard displaying live environmental gauges, predicted temperature vs actual, and regression evaluation curves. [5 Marks]
- Display predicted temperature and residual metrics on the dashboard with interactive manual parameter tuning. [3 Marks]
Flow: Environmental Data Source -> CoAP Server -> CoAP Client -> Linear Regression Model -> Streamlit Dashboard.

SHORT QUESTION:
CoAP Environmental Server & Client with Multiple Linear Regression Temperature Prediction and Streamlit Dashboard [30 Marks].

================================================================================
README & SETUP INSTRUCTIONS:
================================================================================
# Question CQ1: CoAP-Based Environmental Monitoring and Linear Regression Prediction

## Problem Statement [30 Marks]
Develop a CoAP-based environmental telemetry and forecasting system to predict temperature using meteorological and particulate parameters.

- **Parameters**: `PM2.5`, `PM10`, `PRES` (hPa), `DEWP` (°C), `WSPM` (m/s)
- **Historical Dataset**: `PRSA_Data_Aotizhongxin_20130301-20170228.csv` (or built-in robust fallback)
- **Target**: `TEMP` (Ambient Temperature in °C)
- **Model**: Multiple Linear Regression (`scikit-learn`)

---

## Architectural Flow
```text
Environmental Telemetry ──> CoAP Server (UDP 5683) ──> CoAP Client ──> Linear Regression Model ──> Streamlit Dashboard
```

---

## Files in this Package
- `server.py`: Asynchronous CoAP Server listening on UDP port 5683 (`/environment`), serving live environmental readings.
- `client.py`: Standalone CLI CoAP Client that queries the server, fits the Linear Regression model, and prints predictions to console.
- `app.py`: Full-featured interactive Streamlit web dashboard and CoAP Client with live gauges and manual tuning sliders.

---

## How to Run

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

### Step 2: Start the CoAP Server
Open Terminal 1:
```bash
python server.py
```
*Expected Server Output:*
```text
============================================================
  CoAP Environmental Server Active on Port 5683
============================================================
Endpoint: coap://127.0.0.1:5683/environment
Waiting for CoAP Client GET requests...
```

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

#### Option A: Run CLI Client
Open Terminal 2:
```bash
python client.py
```

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

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

1. server.py
--------------------------------------------------------------------------------
import asyncio
import json
import random
import aiocoap
import aiocoap.resource as resource

class EnvironmentResource(resource.Resource):

    def __init__(self):
        super().__init__()

    async def render_get(self, request):

        payload_data = {
            "PM2.5": round(random.uniform(15.0, 120.0), 1),
            "PM10": round(random.uniform(30.0, 200.0), 1),
            "PRES": round(random.uniform(995.0, 1025.0), 1),
            "DEWP": round(random.uniform(5.0, 24.0), 1),
            "WSPM": round(random.uniform(0.5, 6.5), 1),
            "actual_temp": round(random.uniform(18.0, 36.0), 1)
        }
        
        json_payload = json.dumps(payload_data, indent=2)
        print("\n[CoAP Server] Received GET request from CoAP Client")
        print(f"[CoAP Server] Serving Payload:\n{json_payload}")

        return aiocoap.Message(
            payload=json_payload.encode("utf-8"),
            content_format=50
        )

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

    server_context = await aiocoap.Context.create_server_context(root, bind=("127.0.0.1", 5683))
    print("=" * 60)
    print("  CoAP Environmental Server Active on Port 5683")
    print("=" * 60)
    print("Endpoint: coap://127.0.0.1:5683/environment")
    print("Serving features: PM2.5, PM10, PRES, DEWP, WSPM")
    print("Waiting for CoAP Client GET requests...\n")

    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 os
import json
import asyncio
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.metrics import r2_score, mean_absolute_error, mean_squared_error
import aiocoap

COAP_SERVER_URI = "coap://127.0.0.1:5683/environment"
FEATURES = ["PM2.5", "PM10", "PRES", "DEWP", "WSPM"]
TARGET = "TEMP"

def load_or_create_dataset():
    possible_paths = [
        "PRSA_Data_Aotizhongxin_20130301-20170228.csv",
        os.path.join(os.path.dirname(__file__), "PRSA_Data_Aotizhongxin_20130301-20170228.csv")
    ]
    for p in possible_paths:
        if os.path.exists(p):
            print(f"[Dataset] Loading PRSA historical dataset from {p}...")
            df = pd.read_csv(p)
            df = df[FEATURES + [TARGET]].dropna()
            return df

    print("[Dataset] Local PRSA CSV not found. Generating synthetic training dataset...")
    np.random.seed(42)
    n = 2000
    pm25 = np.random.uniform(10, 150, n)
    pm10 = pm25 * np.random.uniform(1.2, 1.8, n)
    pres = np.random.uniform(995, 1030, n)
    dewp = np.random.uniform(-5, 26, n)
    wspm = np.random.uniform(0.5, 7.0, n)

    temp = 0.75 * dewp - 0.05 * (pres - 1010) - 0.02 * pm25 + 0.1 * wspm + 15 + np.random.normal(0, 2.5, n)

    return pd.DataFrame({"PM2.5": pm25, "PM10": pm10, "PRES": pres, "DEWP": dewp, "WSPM": wspm, "TEMP": temp})

def train_linear_regression(df):
    X = df[FEATURES]
    y = df[TARGET]

    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.20, random_state=42)
    model = LinearRegression()
    model.fit(X_train, y_train)

    y_pred = model.predict(X_test)
    r2 = r2_score(y_test, y_pred)
    mae = mean_absolute_error(y_test, y_pred)
    rmse = np.sqrt(mean_squared_error(y_test, y_pred))

    print("-" * 55)
    print("  Multiple Linear Regression Model Performance")
    print("-" * 55)
    print(f"  R-squared (R2) Score  : {r2:.4f}")
    print(f"  Mean Absolute Error   : {mae:.2f} °C")
    print(f"  Root Mean Squared Err : {rmse:.2f} °C")
    for feat, coef in zip(FEATURES, model.coef_):
        print(f"  Coefficient [{feat:>5}] : {coef:+.4f}")
    print(f"  Intercept             : {model.intercept_:+.4f}")
    print("-" * 55)

    return model

async def fetch_coap_environment():
    print(f"\n[CoAP Client] Initiating CoAP GET to {COAP_SERVER_URI}...")
    context = await aiocoap.Context.create_client_context()
    request = aiocoap.Message(code=aiocoap.GET, uri=COAP_SERVER_URI)

    try:
        response = await context.request(request).response
        payload = json.loads(response.payload.decode("utf-8"))
        print("[CoAP Client] Response Received Successfully!")
        return payload
    except Exception as e:
        print(f"[CoAP Client Error] {e}")
        return None

def main():
    print("=" * 60)
    print("  CQ1: CoAP Client & Linear Regression Pipeline")
    print("=" * 60)

    df = load_or_create_dataset()
    model = train_linear_regression(df)

    data = asyncio.run(fetch_coap_environment())
    if not data:
        print("[Error] Could not fetch live CoAP data. Make sure server.py is running.")
        return

    print("\n--- Live Readings Received via CoAP ---")
    for k, v in data.items():
        print(f"  {k:<12}: {v}")

    live_features = [[data["PM2.5"], data["PM10"], data["PRES"], data["DEWP"], data["WSPM"]]]
    pred_temp = model.predict(live_features)[0]

    print("\n" + "=" * 55)
    print(f"  PREDICTED TEMPERATURE : {pred_temp:.2f} °C")
    if "actual_temp" in data:
        actual = data["actual_temp"]
        print(f"  ACTUAL TEMPERATURE    : {actual:.2f} °C")
        print(f"  ABSOLUTE ERROR        : {abs(pred_temp - actual):.2f} °C")
    print("=" * 55)

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

3. app.py
--------------------------------------------------------------------------------
import os
import json
import asyncio
import numpy as np
import pandas as pd
import streamlit as st
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.metrics import r2_score, mean_absolute_error, mean_squared_error
import aiocoap

st.set_page_config(page_title="CQ1: CoAP Linear Regression Predictor", layout="wide", page_icon="🌡️")

COAP_URI = "coap://127.0.0.1:5683/environment"
FEATURES = ["PM2.5", "PM10", "PRES", "DEWP", "WSPM"]
TARGET = "TEMP"

@st.cache_resource
def load_and_train_model():
    possible_paths = [
        "PRSA_Data_Aotizhongxin_20130301-20170228.csv",
        os.path.join(os.path.dirname(__file__), "PRSA_Data_Aotizhongxin_20130301-20170228.csv")
    ]
    df = None
    for p in possible_paths:
        if os.path.exists(p):
            df = pd.read_csv(p)[FEATURES + [TARGET]].dropna()
            break

    if df is None:
        np.random.seed(42)
        n = 2000
        pm25 = np.random.uniform(10, 150, n)
        pm10 = pm25 * np.random.uniform(1.2, 1.8, n)
        pres = np.random.uniform(995, 1030, n)
        dewp = np.random.uniform(-5, 26, n)
        wspm = np.random.uniform(0.5, 7.0, n)
        temp = 0.75 * dewp - 0.05 * (pres - 1010) - 0.02 * pm25 + 0.1 * wspm + 15 + np.random.normal(0, 2.5, n)
        df = pd.DataFrame({"PM2.5": pm25, "PM10": pm10, "PRES": pres, "DEWP": dewp, "WSPM": wspm, "TEMP": temp})

    X = df[FEATURES]
    y = df[TARGET]
    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.20, random_state=42)

    model = LinearRegression()
    model.fit(X_train, y_train)
    y_pred = model.predict(X_test)

    metrics = {
        "r2": r2_score(y_test, y_pred),
        "mae": mean_absolute_error(y_test, y_pred),
        "rmse": np.sqrt(mean_squared_error(y_test, y_pred)),
        "coefficients": dict(zip(FEATURES, model.coef_)),
        "intercept": model.intercept_
    }
    return model, metrics, df

async def fetch_coap_payload():
    context = await aiocoap.Context.create_client_context()
    msg = aiocoap.Message(code=aiocoap.GET, uri=COAP_URI)
    resp = await context.request(msg).response
    return json.loads(resp.payload.decode("utf-8"))

def main():
    st.title("🌡️ CoAP Environmental Telemetry & Temperature Predictor")
    st.markdown("**CAE 2 Combined Question 1**: CoAP Protocol + Multiple Linear Regression (`aiocoap` + `scikit-learn` + `Streamlit`)")
    st.markdown("---")

    model, metrics, df = load_and_train_model()

    with st.sidebar:
        st.header("📊 Model Metrics")
        st.metric("R² Variance Score", f"{metrics['r2']:.4f}")
        st.metric("Mean Absolute Error (MAE)", f"{metrics['mae']:.2f} °C")
        st.metric("Root Mean Squared Error", f"{metrics['rmse']:.2f} °C")
        st.markdown("---")
        st.subheader("Model Formula")
        st.code(
            f"TEMP = {metrics['intercept']:.2f}\n" +
            "\n".join([f"  + ({coef:+.3f} * {feat})" for feat, coef in metrics['coefficients'].items()]),
            language="text"
        )

    col_act, col_info = st.columns([1, 3])
    with col_act:
        fetch_clicked = st.button("📡 Fetch Live Data via CoAP", type="primary", use_container_width=True)
    with col_info:
        st.info(f"Target CoAP Endpoint: `{COAP_URI}` (Make sure `python server.py` is running)")

    if "live_data" not in st.session_state:
        st.session_state.live_data = {
            "PM2.5": 45.0, "PM10": 78.0, "PRES": 1012.0, "DEWP": 18.5, "WSPM": 2.2, "actual_temp": 27.5
        }

    if fetch_clicked:
        try:
            live = asyncio.run(fetch_coap_payload())
            st.session_state.live_data = live
            st.success("Successfully fetched telemetry from CoAP Server!")
        except Exception as e:
            st.error(f"Failed to connect to CoAP Server: {e}. Using cached/manual readings.")

    data = st.session_state.live_data

    st.subheader("1. Live Environmental Parameters (via CoAP)")
    m1, m2, m3, m4, m5 = st.columns(5)
    m1.metric("PM2.5", f"{data['PM2.5']} µg/m³")
    m2.metric("PM10", f"{data['PM10']} µg/m³")
    m3.metric("Pressure", f"{data['PRES']} hPa")
    m4.metric("Dew Point", f"{data['DEWP']} °C")
    m5.metric("Wind Speed", f"{data['WSPM']} m/s")

    st.subheader("2. Real-Time Regression Inference")
    input_vector = [[data["PM2.5"], data["PM10"], data["PRES"], data["DEWP"], data["WSPM"]]]
    pred_temp = model.predict(input_vector)[0]

    p1, p2, p3 = st.columns(3)
    p1.metric("Predicted Temperature", f"{pred_temp:.2f} °C", delta=f"{pred_temp - 25.0:+.1f} vs 25°C ref")
    actual = data.get("actual_temp", 26.0)
    p2.metric("Actual Sensor Temp", f"{actual:.2f} °C")
    p3.metric("Absolute Residual Error", f"{abs(pred_temp - actual):.2f} °C", delta_color="inverse")

    st.markdown("---")

    st.subheader("3. Interactive Scenario Simulation (Manual Inputs)")
    c1, c2, c3, c4, c5 = st.columns(5)
    in_pm25 = c1.slider("Simulate PM2.5", 0.0, 300.0, float(data["PM2.5"]))
    in_pm10 = c2.slider("Simulate PM10", 0.0, 400.0, float(data["PM10"]))
    in_pres = c3.slider("Simulate Pressure", 970.0, 1040.0, float(data["PRES"]))
    in_dewp = c4.slider("Simulate Dew Point", -10.0, 30.0, float(data["DEWP"]))
    in_wspm = c5.slider("Simulate Wind Speed", 0.0, 15.0, float(data["WSPM"]))

    sim_pred = model.predict([[in_pm25, in_pm10, in_pres, in_dewp, in_wspm]])[0]
    st.info(f"🔮 **Predicted Ambient Temperature for Simulated Inputs:** **{sim_pred:.2f} °C**")

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