================================================================================
  Question CQ2: CoAP Room Telemetry & Decision Tree Occupancy Classifier [30 Marks]
================================================================================

FULL QUESTION:
CAE 2 IoT Practical Exam - Combined Question:
CQ2. CoAP-Based Room Occupancy Monitoring and Decision Tree Classification [30 Marks]
Develop a smart building occupancy monitoring and classification system using CoAP protocol and Decision Tree model.
Input Parameters: Temperature, Relative Humidity, Light (Lux), and CO2 (ppm).
Historical Dataset: UCI Room Occupancy Detection dataset (datatraining.txt).
Target Variable: Occupancy (0 = Not Occupied, 1 = Occupied).
The system should:
- Develop a CoAP Server and CoAP Client using UDP port 5683. [6 Marks]
- Retrieve live/simulated multi-sensor room telemetry via CoAP Server. [6 Marks]
- Transfer data to CoAP Client using CoAP GET request. [5 Marks]
- Train a Decision Tree Classifier on historical datatraining.txt dataset to predict room occupancy based on Temperature, Humidity, Light, and CO2. Evaluate model using Accuracy Score and Confusion Matrix. [5 Marks]
- Develop a Streamlit web dashboard displaying live sensor parameters, real-time Occupancy status badge (Occupied / Vacant), and Confusion Matrix. [5 Marks]
- Display classification output dynamically with an interactive slider panel for manual testing. [3 Marks]
Flow: Room Sensors -> CoAP Server -> CoAP Client -> Decision Tree Model -> Streamlit Dashboard.

SHORT QUESTION:
CoAP Room Telemetry Server & Client with Decision Tree Occupancy Classification and Streamlit Dashboard [30 Marks].

================================================================================
README & SETUP INSTRUCTIONS:
================================================================================
# Question CQ2: CoAP-Based Room Occupancy Classifier

## Problem Statement [30 Marks]
Implement a CoAP smart office pipeline where room sensor readings (Temperature, Humidity, Light, CO2) are transmitted over CoAP and classified into Occupied (1) or Vacant (0) using a Decision Tree model.

- **Parameters**: `Temperature` (°C), `Humidity` (%), `Light` (Lux), `CO2` (ppm)
- **Dataset**: UCI Occupancy Detection (`datatraining.txt` or synthetic fallback)
- **Target**: `Occupancy` (0 = Vacant, 1 = Occupied)
- **Model**: Decision Tree Classifier (`scikit-learn`)

---

## Architectural Flow
```text
Room Telemetry ──> CoAP Server (UDP 5683) ──> CoAP Client ──> Decision Tree Classifier ──> Streamlit UI
```

---

## Files in this Package
- `server.py`: Asynchronous CoAP Server listening on UDP port 5683 (`/room`), serving live room telemetry.
- `client.py`: Standalone CLI CoAP Client that fetches data, trains Decision Tree, and prints occupancy prediction with confusion matrix.
- `app.py`: Full-featured interactive Streamlit web dashboard and CoAP Client with visual status alert and confusion matrix.

---

## 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 Room Occupancy Server Active on Port 5683
============================================================
Endpoint: coap://127.0.0.1:5683/room
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 Room Data via CoAP"** to initiate a CoAP GET request and view live predictions!

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

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

class RoomResource(resource.Resource):

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

    async def render_get(self, request):

        is_occupied_profile = random.choice([True, False])
        if is_occupied_profile:
            temp = round(random.uniform(21.5, 24.5), 2)
            hum = round(random.uniform(25.0, 35.0), 2)
            light = round(random.uniform(350.0, 650.0), 1)
            co2 = round(random.uniform(650.0, 1400.0), 1)
        else:
            temp = round(random.uniform(19.0, 21.0), 2)
            hum = round(random.uniform(20.0, 28.0), 2)
            light = round(random.uniform(0.0, 50.0), 1)
            co2 = round(random.uniform(400.0, 520.0), 1)

        payload_data = {
            "Temperature": temp,
            "Humidity": hum,
            "Light": light,
            "CO2": co2
        }

        json_payload = json.dumps(payload_data, indent=2)
        print("\n[CoAP Server] Received GET request from CoAP Client")
        print(f"[CoAP Server] Serving Room 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(["room"], RoomResource())

    server_context = await aiocoap.Context.create_server_context(root, bind=("127.0.0.1", 5683))
    print("=" * 60)
    print("  CoAP Room Occupancy Server Active on Port 5683")
    print("=" * 60)
    print("Endpoint: coap://127.0.0.1:5683/room")
    print("Serving features: Temperature, Humidity, Light, CO2")
    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.tree import DecisionTreeClassifier
from sklearn.metrics import accuracy_score, confusion_matrix, classification_report
import aiocoap

COAP_SERVER_URI = "coap://127.0.0.1:5683/room"
FEATURES = ["Temperature", "Humidity", "Light", "CO2"]
TARGET = "Occupancy"

def load_or_create_dataset():
    possible_paths = [
        "datatraining.txt",
        os.path.join(os.path.dirname(__file__), "datatraining.txt"),
        os.path.join("..", "datatraining.txt")
    ]
    for p in possible_paths:
        if os.path.exists(p):
            print(f"[Dataset] Loading UCI Occupancy dataset from {p}...")
            df = pd.read_csv(p)
            df.columns = [c.replace('"', '').strip() for c in df.columns]
            if all(col in df.columns for col in FEATURES + [TARGET]):
                return df[FEATURES + [TARGET]].dropna()

    print("[Dataset] datatraining.txt not found. Generating synthetic occupancy dataset...")
    np.random.seed(42)
    n = 2000

    y = np.random.choice([0, 1], size=n)
    temp = np.where(y == 1, np.random.uniform(21.5, 24.5, n), np.random.uniform(19.0, 21.0, n))
    hum = np.where(y == 1, np.random.uniform(25.0, 35.0, n), np.random.uniform(20.0, 28.0, n))
    light = np.where(y == 1, np.random.uniform(300.0, 600.0, n), np.random.uniform(0.0, 50.0, n))
    co2 = np.where(y == 1, np.random.uniform(600.0, 1400.0, n), np.random.uniform(400.0, 520.0, n))

    return pd.DataFrame({"Temperature": temp, "Humidity": hum, "Light": light, "CO2": co2, "Occupancy": y})

def train_decision_tree(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, stratify=y)
    model = DecisionTreeClassifier(max_depth=5, random_state=42)
    model.fit(X_train, y_train)

    y_pred = model.predict(X_test)
    acc = accuracy_score(y_test, y_pred)
    cm = confusion_matrix(y_test, y_pred)

    print("-" * 55)
    print("  Decision Tree Room Occupancy Classifier Performance")
    print("-" * 55)
    print(f"  Test Accuracy Score : {acc * 100:.2f}%")
    print("  Confusion Matrix:")
    print(f"    [[TN={cm[0][0]:>4}, FP={cm[0][1]:>4}]")
    print(f"     [FN={cm[1][0]:>4}, TP={cm[1][1]:>4}]]")
    print("-" * 55)

    return model

async def fetch_coap_room():
    print(f"\n[CoAP Client] Querying CoAP Server at {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] Data Received Successfully!")
        return payload
    except Exception as e:
        print(f"[CoAP Client Error] {e}")
        return None

def main():
    print("=" * 60)
    print("  CQ2: CoAP Client & Decision Tree Occupancy Pipeline")
    print("=" * 60)

    df = load_or_create_dataset()
    model = train_decision_tree(df)

    data = asyncio.run(fetch_coap_room())
    if not data:
        print("[Error] Make sure server.py is running on port 5683.")
        return

    print("\n--- Live Room Telemetry (via CoAP) ---")
    for k, v in data.items():
        print(f"  {k:<14}: {v}")

    features = [[data["Temperature"], data["Humidity"], data["Light"], data["CO2"]]]
    prediction = model.predict(features)[0]
    prob = model.predict_proba(features)[0]

    status = "ROOM OCCUPIED" if prediction == 1 else "ROOM VACANT (NOT OCCUPIED)"
    print("\n" + "=" * 55)
    print(f"  STATUS PREDICTION   : {status}")
    print(f"  CONFIDENCE PROB     : Vacant={prob[0]*100:.1f}%, Occupied={prob[1]*100:.1f}%")
    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.tree import DecisionTreeClassifier
from sklearn.metrics import accuracy_score, confusion_matrix
import aiocoap

st.set_page_config(page_title="CQ2: CoAP Occupancy Classifier", layout="wide", page_icon="🏢")

COAP_URI = "coap://127.0.0.1:5683/room"
FEATURES = ["Temperature", "Humidity", "Light", "CO2"]
TARGET = "Occupancy"

@st.cache_resource
def load_and_train_model():
    possible_paths = [
        "datatraining.txt",
        os.path.join(os.path.dirname(__file__), "datatraining.txt"),
        os.path.join("..", "datatraining.txt")
    ]
    df = None
    for p in possible_paths:
        if os.path.exists(p):
            df = pd.read_csv(p)
            df.columns = [c.replace('"', '').strip() for c in df.columns]
            if all(col in df.columns for col in FEATURES + [TARGET]):
                df = df[FEATURES + [TARGET]].dropna()
                break

    if df is None:
        np.random.seed(42)
        n = 2000
        y = np.random.choice([0, 1], size=n)
        temp = np.where(y == 1, np.random.uniform(21.5, 24.5, n), np.random.uniform(19.0, 21.0, n))
        hum = np.where(y == 1, np.random.uniform(25.0, 35.0, n), np.random.uniform(20.0, 28.0, n))
        light = np.where(y == 1, np.random.uniform(300.0, 600.0, n), np.random.uniform(0.0, 50.0, n))
        co2 = np.where(y == 1, np.random.uniform(600.0, 1400.0, n), np.random.uniform(400.0, 520.0, n))
        df = pd.DataFrame({"Temperature": temp, "Humidity": hum, "Light": light, "CO2": co2, "Occupancy": y})

    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, stratify=y)

    model = DecisionTreeClassifier(max_depth=5, random_state=42)
    model.fit(X_train, y_train)

    y_pred = model.predict(X_test)
    acc = accuracy_score(y_test, y_pred)
    cm = confusion_matrix(y_test, y_pred)

    return model, acc, cm, 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 Room Telemetry & Decision Tree Occupancy Classifier")
    st.markdown("**CAE 2 Combined Question 2**: CoAP Protocol + Decision Tree Classifier (`aiocoap` + `scikit-learn` + `Streamlit`)")
    st.markdown("---")

    model, acc, cm, df = load_and_train_model()

    with st.sidebar:
        st.header("📊 Model Metrics")
        st.metric("Test Accuracy Score", f"{acc * 100:.2f}%")
        st.markdown("---")
        st.subheader("Confusion Matrix")
        cm_df = pd.DataFrame(cm, index=["Actual Vacant (0)", "Actual Occupied (1)"],
                             columns=["Pred Vacant (0)", "Pred Occupied (1)"])
        st.dataframe(cm_df, use_container_width=True)

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

    if "live_room" not in st.session_state:
        st.session_state.live_room = {"Temperature": 22.4, "Humidity": 27.2, "Light": 435.0, "CO2": 820.0}

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

    data = st.session_state.live_room

    st.subheader("1. Live Room Parameters (via CoAP)")
    c1, c2, c3, c4 = st.columns(4)
    c1.metric("Temperature", f"{data['Temperature']} °C")
    c2.metric("Relative Humidity", f"{data['Humidity']} %")
    c3.metric("Light Level", f"{data['Light']} Lux")
    c4.metric("CO₂ Concentration", f"{data['CO2']} ppm")

    st.subheader("2. Real-Time Occupancy Classification")
    features = [[data["Temperature"], data["Humidity"], data["Light"], data["CO2"]]]
    prediction = model.predict(features)[0]
    probs = model.predict_proba(features)[0]

    col_stat, col_prob = st.columns([2, 2])
    with col_stat:
        if prediction == 1:
            st.error("### 🔴 STATUS: ROOM OCCUPIED")
            st.write("People detected in room. Ventilation & Lighting active.")
        else:
            st.success("### 🟢 STATUS: ROOM VACANT (NOT OCCUPIED)")
            st.write("No people detected in room. Energy saving mode eligible.")

    with col_prob:
        st.write(f"**Confidence Probabilities:**")
        st.progress(float(probs[1]))
        st.caption(f"Occupied: {probs[1]*100:.1f}% | Vacant: {probs[0]*100:.1f}%")

    st.markdown("---")

    st.subheader("3. Manual Parameter Simulator")
    s1, s2, s3, s4 = st.columns(4)
    sim_t = s1.slider("Temperature (°C)", 16.0, 30.0, float(data["Temperature"]))
    sim_h = s2.slider("Humidity (%)", 15.0, 50.0, float(data["Humidity"]))
    sim_l = s3.slider("Light (Lux)", 0.0, 800.0, float(data["Light"]))
    sim_c = s4.slider("CO2 (ppm)", 350.0, 2000.0, float(data["CO2"]))

    sim_pred = model.predict([[sim_t, sim_h, sim_l, sim_c]])[0]
    if sim_pred == 1:
        st.error("Simulated Outcome: **ROOM OCCUPIED** (High light/CO2 signature)")
    else:
        st.success("Simulated Outcome: **ROOM VACANT** (Low light/ambient CO2)")

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