Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion backend/devices_json.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
import json
import asyncio
import random
import os

BASE_DIR = os.path.dirname(os.path.abspath(__file__))

deviceFile = "devices.json"
selectedUserFile = "selected_user.json"
usersDBFile = os.path.abspath(os.path.join(BASE_DIR, "../database/users_db.json"))

selected_user_devices = "selected_user_devices.json"

updates = [] # Stores messages for frontend

Expand Down Expand Up @@ -148,4 +155,4 @@ def getUpdates():
global updates
messages = updates[:]
updates.clear()
return messages
return messages
32 changes: 32 additions & 0 deletions backend/energy/daily_energy.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
[
{ "timestamp": "2025-03-01T00:00:00", "power_usage": 4800 },
{ "timestamp": "2025-03-02T00:00:00", "power_usage": 5120 },
{ "timestamp": "2025-03-03T00:00:00", "power_usage": 4500 },
{ "timestamp": "2025-03-04T00:00:00", "power_usage": 4700 },
{ "timestamp": "2025-03-05T00:00:00", "power_usage": 5300 },
{ "timestamp": "2025-03-06T00:00:00", "power_usage": 5100 },
{ "timestamp": "2025-03-07T00:00:00", "power_usage": 4900 },
{ "timestamp": "2025-03-08T00:00:00", "power_usage": 4780 },
{ "timestamp": "2025-03-09T00:00:00", "power_usage": 5000 },
{ "timestamp": "2025-03-10T00:00:00", "power_usage": 5150 },
{ "timestamp": "2025-03-11T00:00:00", "power_usage": 5600 },
{ "timestamp": "2025-03-12T00:00:00", "power_usage": 5400 },
{ "timestamp": "2025-03-13T00:00:00", "power_usage": 5900 },
{ "timestamp": "2025-03-14T00:00:00", "power_usage": 4700 },
{ "timestamp": "2025-03-15T00:00:00", "power_usage": 6000 },
{ "timestamp": "2025-03-16T00:00:00", "power_usage": 6300 },
{ "timestamp": "2025-03-17T00:00:00", "power_usage": 6250 },
{ "timestamp": "2025-03-18T00:00:00", "power_usage": 5950 },
{ "timestamp": "2025-03-19T00:00:00", "power_usage": 6400 },
{ "timestamp": "2025-03-20T00:00:00", "power_usage": 6600 },
{ "timestamp": "2025-03-21T00:00:00", "power_usage": 6100 },
{ "timestamp": "2025-03-22T00:00:00", "power_usage": 6800 },
{ "timestamp": "2025-03-23T00:00:00", "power_usage": 6700 },
{ "timestamp": "2025-03-24T00:00:00", "power_usage": 7150 },
{ "timestamp": "2025-03-25T00:00:00", "power_usage": 6800 },
{ "timestamp": "2025-03-26T00:00:00", "power_usage": 7000 },
{ "timestamp": "2025-03-27T00:00:00", "power_usage": 7500 },
{ "timestamp": "2025-03-28T00:00:00", "power_usage": 7400 },
{ "timestamp": "2025-03-29T00:00:00", "power_usage": 7100 },
{ "timestamp": "2025-03-30T00:00:00", "power_usage": 6900 }
]
14 changes: 14 additions & 0 deletions backend/energy/monthly_energy.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[
{ "timestamp": "2024-01-01T00:00:00", "power_usage": 150000 },
{ "timestamp": "2024-02-01T00:00:00", "power_usage": 153500 },
{ "timestamp": "2024-03-01T00:00:00", "power_usage": 160000 },
{ "timestamp": "2024-04-01T00:00:00", "power_usage": 170000 },
{ "timestamp": "2024-05-01T00:00:00", "power_usage": 165000 },
{ "timestamp": "2024-06-01T00:00:00", "power_usage": 180000 },
{ "timestamp": "2024-07-01T00:00:00", "power_usage": 175000 },
{ "timestamp": "2024-08-01T00:00:00", "power_usage": 185500 },
{ "timestamp": "2024-09-01T00:00:00", "power_usage": 190500 },
{ "timestamp": "2024-10-01T00:00:00", "power_usage": 195000 },
{ "timestamp": "2024-11-01T00:00:00", "power_usage": 200500 },
{ "timestamp": "2024-12-01T00:00:00", "power_usage": 210000 }
]
22 changes: 19 additions & 3 deletions backend/energy_json.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
import devices_json as dj
import random
import json
import os

deviceFile = "energy_tips.json"
ENERGY_FOLDER = os.path.join(os.path.dirname(__file__), "energy")
print(ENERGY_FOLDER)

def get_energy_data(time_range: str):
"""Fetch energy usage data from JSON files."""

if time_range not in ["daily", "monthly"]:
return {"error": "Invalid time range. Use 'daily' or 'monthly'."}

file_path = os.path.join(ENERGY_FOLDER, f"{time_range}_energy.json")

try:
with open(file_path, "r") as file:
return json.load(file)
except FileNotFoundError:
return {"error": f"{time_range}_energy.json not found."}
except json.JSONDecodeError:
return {"error": f"Invalid JSON format in {time_range}_energy.json."}
19 changes: 17 additions & 2 deletions backend/fastAPI.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
from typing import List, Optional
from pydantic import BaseModel

import devices_json as dj
import users
import energy_json as ej

import asyncio
import os
import json

from fastapi import FastAPI
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware

# Serve user data (Only for testing purposes)
Expand Down Expand Up @@ -108,4 +110,17 @@ def add_new_user(user: UserRequest):
@app.delete("/delete_user/{user_name}/{user_password}")
def delete_user(user_name: str, user_password: str):
"""Deletes a user with the given name and password."""
return users.delete_user(user_name, user_password)
return users.delete_user(user_name, user_password)

@app.get("/energy_usage")
def fetch_energy_usage(range: str):
if range == "daily":
return ej.get_energy_data("daily")
elif range == "monthly":
return ej.get_energy_data("monthly")
else:
raise HTTPException(status_code=400, detail="Invalid range")

@app.get("/energy_usage/{time_range}")
def fetch_energy_usage(time_range: str):
return ej.get_energy_data(time_range)
2 changes: 1 addition & 1 deletion database/users_db.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"user_id": 2,
"user_name": "David F",
"user_password": "1415",
"allocated_devices": [],
"allocated_devices": ["7", "8"],
"user_role": "sub_user"
},
{
Expand Down
40 changes: 28 additions & 12 deletions frontend/src/app/dashboard/page.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ const Dashboard = () => {
const [data, setData] = useState([]);
const [checked, setChecked] = useState([]);
const [timeRange, setTimeRange] = useState("realtime");
const [energyData, setEnergyData] = useState({ daily: [], monthly: [] });

const theme = useTheme();
const boxShadow =
Expand All @@ -60,11 +61,12 @@ const Dashboard = () => {
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch("http://localhost:8000/device_info");
const result = await response.json();
// Fetch device data
const deviceResponse = await fetch("http://localhost:8000/device_info");
const deviceResult = await deviceResponse.json();

if (result && Array.isArray(result.smart_home_devices)) {
const connectedDevices = result.smart_home_devices.filter(
if (deviceResult && Array.isArray(deviceResult.smart_home_devices)) {
const connectedDevices = deviceResult.smart_home_devices.filter(
(device) => device.connection_status === "connected"
);
setData(connectedDevices);
Expand All @@ -74,15 +76,26 @@ const Dashboard = () => {
.map((device) => device.id);
setChecked(initialChecked);
} else {
console.error("Invalid response structure", result);
console.error("Invalid response structure", deviceResult);
}

const dailyResponse = await fetch("http://localhost:8000/energy_usage/daily");
const dailyResult = await dailyResponse.json();

const monthlyResponse = await fetch("http://localhost:8000/energy_usage/monthly");
const monthlyResult = await monthlyResponse.json();

setEnergyData({
daily: dailyResult,
monthly: monthlyResult,
});
} catch (error) {
console.error("Error fetching smart home devices:", error);
console.error("Error fetching data:", error);
}
};

fetchData();
const interval = setInterval(fetchData, 1000);
const interval = setInterval(fetchData, 1000); // Update every 10 seconds
return () => clearInterval(interval);
}, []);

Expand Down Expand Up @@ -232,7 +245,7 @@ const Dashboard = () => {
textDecoration: "none",
color: "inherit",
transition: "transform 0.2s ease-in-out",
"&:hover": { transform: "scale(1.01)" }, // Slight hover effect
"&:hover": { transform: "scale(1.01)" },
}}
>
<CardContent
Expand Down Expand Up @@ -320,10 +333,10 @@ const Dashboard = () => {
flexDirection: "column",
// boxShadow: boxShadow,
width: "100%",
textDecoration: "none", // Prevents underline
color: "inherit", // Keeps original text color
textDecoration: "none",
color: "inherit",
transition: "transform 0.2s ease-in-out",
"&:hover": { transform: "scale(1.01)" }, // Slight hover effect
"&:hover": { transform: "scale(1.01)" },
}}
>
<CardContent
Expand All @@ -334,7 +347,10 @@ const Dashboard = () => {
width: "100%",
}}
>
<EnergyUsageChart data={data} />
<EnergyUsageChart
data={timeRange === "daily" ? energyData.daily : timeRange === "monthly" ? energyData.monthly : data}
timeRange={timeRange}
/>
<ButtonGroup
sx={{ marginTop: "auto", alignSelf: "center" }}
color="primary"
Expand Down
102 changes: 54 additions & 48 deletions frontend/src/app/ui/energyChart.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,63 +11,60 @@ import Typography from "@mui/material/Typography";
import dayjs from "dayjs";
import { useTheme } from "@emotion/react";

const EnergyUsageChart = ({ data }) => {
const EnergyUsageChart = ({ data, timeRange }) => {
const [dataPoints, setDataPoints] = useState([]);
const [buffer, setBuffer] = useState([]); // Store data for averaging
const chartRef = useRef(null);

const theme = useTheme();
const strokeColor = theme.palette.mode === "dark" ? "#8253d7" : "#1F99FC";

// Function to get current power usage
const getCurrentPowerUsage = () => {
if (!data || data.length === 0) return 0;
return data.reduce((acc, device) => acc + (device.power_usage || 0), 0);
};

// Initialize with first data point
useEffect(() => {
const initialPower = getCurrentPowerUsage();
setDataPoints([{ time: new Date(), power: initialPower }]);
}, []); // Runs only on first render

// Update buffer every second with new power usage
useEffect(() => {
if (!data || data.length === 0) return;

const currentPower = getCurrentPowerUsage();
setBuffer((prevBuffer) => {
const newBuffer = [...prevBuffer, currentPower];

// Keep only the last 5 minutes (assuming data updates every second)
return newBuffer.length > 300 ? newBuffer.slice(-300) : newBuffer;
});
}, [data]);

// Every 5 minutes, plot the average power usage
useEffect(() => {
const interval = setInterval(() => {
if (buffer.length === 0) return;

const averagePower =
buffer.reduce((acc, val) => acc + val, 0) / buffer.length || 0;
const timestamp = new Date();

if (!isNaN(averagePower) && averagePower !== undefined) {
if (timeRange === "realtime") {
const fetchRealTimeData = () => {
const currentPower = getCurrentPowerUsage();
setDataPoints((prevData) => {
const newData = [
...prevData,
{ time: timestamp, power: averagePower },
{ time: new Date(), power: currentPower },
];
return newData.length > 10 ? newData.slice(1) : newData;
});
}
};

const interval = setInterval(fetchRealTimeData, 1000);
return () => clearInterval(interval);
}
}, [data, timeRange]);

setBuffer([]);
}, 1000);
useEffect(() => {
if (timeRange === "daily" || timeRange === "monthly") {
const fetchHistoricalData = async () => {
try {
const response = await fetch(
`http://localhost:8000/energy_usage?range=${timeRange}`
);
const result = await response.json();
if (Array.isArray(result)) {
setDataPoints(
result.map((entry) => ({
time: new Date(entry.timestamp),
power: entry.power_usage,
}))
);
}
} catch (error) {
console.error("Error fetching historical energy data:", error);
}
};

return () => clearInterval(interval);
}, [buffer]);
fetchHistoricalData();
}
}, [timeRange]);

return (
<>
Expand All @@ -82,23 +79,34 @@ const EnergyUsageChart = ({ data }) => {
color: "primary.main",
}}
>
Energy Usage Trend
{timeRange === "realtime"
? "Real-time Energy Usage"
: timeRange === "daily"
? "Daily Energy Usage"
: "Monthly Energy Usage"}
</Typography>

{/* Ensure chart only renders with valid data */}
{dataPoints.length > 0 ? (
<ResponsiveContainer width="100%" height={300} className="mt-10">
<ResponsiveContainer width="100%" height={300}>
<LineChart data={dataPoints}>
<XAxis
dataKey="time"
tickFormatter={(time) => dayjs(time).format("HH:mm")}
/>
<YAxis
label={{ value: "Power (W)", angle: -90, position: "insideLeft" }}
tickFormatter={(time) =>
timeRange === "monthly"
? dayjs(time).format("MMM DD")
: timeRange === "daily"
? dayjs(time).format("MMM DD")
: dayjs(time).format("HH:mm")
}
/>
<YAxis label={{ value: "Power (W)", angle: -90, position: "insideLeft" }} />
<Tooltip
labelFormatter={(label) =>
`Time: ${dayjs(label).format("HH:mm:ss")}`
`Time: ${
timeRange === "monthly"
? dayjs(label).format("MMM DD")
: dayjs(label).format("HH:mm")
}`
}
formatter={(value) => [`${value} W`, "Power Usage"]}
/>
Expand All @@ -111,9 +119,7 @@ const EnergyUsageChart = ({ data }) => {
</LineChart>
</ResponsiveContainer>
) : (
<Typography
sx={{ fontSize: 16, fontStyle: "italic", textAlign: "center" }}
>
<Typography sx={{ fontSize: 16, fontStyle: "italic", textAlign: "center" }}>
No data available yet...
</Typography>
)}
Expand Down