Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
99561d4
Set the bare bones of the automation scheduling
david-g-f Feb 25, 2025
9683d3f
Created the skeleton of how automations are stored
david-g-f Feb 25, 2025
b2a34bf
First commit for automations script
david-g-f Feb 25, 2025
225a5b4
Uncomplete skeleton for automations.json
david-g-f Feb 25, 2025
2e3b970
Backbones of the automations.json created
david-g-f Feb 25, 2025
2d6a7c8
Merge branch 'main' into automation
adityashibu Feb 25, 2025
3be2c86
Automation JSON load/save functions written
david-g-f Feb 25, 2025
91cd4e7
Written addAutomation function
david-g-f Feb 25, 2025
ad66ff1
Included critical attribute to automation json
david-g-f Feb 25, 2025
b25b1a3
Added automation status function
david-g-f Feb 25, 2025
3d8a400
Made JSON a bit clearer
david-g-f Feb 25, 2025
cb5bdc7
Added delete automation function
david-g-f Feb 25, 2025
25e1705
Add button for adding automation schedules in the Automations page
adityashibu Feb 25, 2025
981ce83
Added quick FastAPI route + helper function for scheduling purposes
david-g-f Feb 25, 2025
3d3bf06
Add dialog for creating automation schedules with time picker and con…
adityashibu Feb 25, 2025
9e60b5a
Added critical attribute
david-g-f Feb 25, 2025
217d2ba
Merge branch 'automation' into automation
adityashibu Feb 25, 2025
2fbf928
MERGE CONFLICTS RESOLVED
adityashibu Feb 25, 2025
a9701f5
Added users python file to handle user login requests
adityashibu Feb 26, 2025
7d01b3d
Update devices_db.py according to changes in devices.json have to set…
jyspeach Feb 27, 2025
9adb141
Update users_db.json to call devices by id
jyspeach Feb 27, 2025
e3e86a9
Initialize selected_user variable in users.py
adityashibu Feb 27, 2025
a0e175a
Update users_db.json to include user_id
jyspeach Feb 28, 2025
0d16606
fixed env (bug: is_json_updated function)
Feb 28, 2025
f5c3428
Merge branch 'auth' of https://github.com/adityashibu/Production into…
Feb 28, 2025
c5c3cad
Merge branch 'auth'
adityashibu Feb 28, 2025
4874180
added user key
Feb 28, 2025
00e747e
Merge branch 'main' of https://github.com/adityashibu/Production
Feb 28, 2025
4dfc9d7
Merge branch 'PowerHouse-Project:automation' into automation
adityashibu Feb 28, 2025
f4adc03
Merge branch 'automation'
adityashibu Feb 28, 2025
669f9b7
Update firestore if any changes happen to the json/app
jyspeach Mar 2, 2025
af9ed12
Create .env for firestore
jyspeach Mar 2, 2025
4e92a72
Update time range selection in dashboard to include realtime option
adityashibu Mar 2, 2025
d2ddd67
Merge branch 'database'
adityashibu Mar 2, 2025
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
12 changes: 12 additions & 0 deletions backend/automations.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"automation_example": [
{
"id": 1,
"device": "absolute device name goes here",
"name": "Name goes here",
"enabled": true,
"triggers": "time goes here (in 24 hour format)",
"action": "function call goes here"
}
]
}
58 changes: 58 additions & 0 deletions backend/automations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import devices_json as dj
import schedule

automationFile = "automations.json"

import json
import schedule

automationFile = "automations.json"
updates = []


def loadAutomations():
try:
with open(automationFile, "r") as JSONfile:
return json.load(JSONfile)
except FileNotFoundError:
updates.append("Error: automations.json not found!")
return {"automations": []}


def saveAutomations(data):
with open(automationFile, "w") as JSONfile:
json.dump(data, JSONfile, indent=2)


def addAutomation(automation): # Add automation to automations.json
data = loadAutomations()
automations = data.get("automations", [])
automations.append(automation)
saveAutomations(data)
updates.append(f"Added {automation['name']} to the automation table!")


# Manipulation of automation schedules down below
def changeAutomationStatus(id):
data = loadAutomations()
automations = data.get("automations", [])
for automation in automations:
if automation["id"] == id:
automation["enabled"] = not automation["enabled"]
saveAutomations(data)
updates.append(f"Changed status of {automation['name']} to {automation['enabled']}!")
return
updates.append("Error: Automation with specified ID not found (backend error!)")
return

def deleteAutomation(id):
data = loadAutomations()
automations = data.get("automations", [])
for automation in automations:
if automation["id"] == id:
automations.remove(automation)
saveAutomations(data)
updates.append(f"Deleted {automation['name']} from the automation table!")
return
updates.append("Error: Automation with specified ID not found (backend error!)")
return
3 changes: 3 additions & 0 deletions backend/devices_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,9 @@ def sumRating():
devices = data.get("smart_home_devices", [])
return sum(device["power_rating"] for device in devices)

def deviceFunctions(): # Returns the list of device functions for scheduling purposes and possibly miscellaneous purposes
return ["Set Oven Timer", "Change Device Status"]

async def updateDevices():
while True:
data = loadJSON()
Expand Down
5 changes: 5 additions & 0 deletions backend/fastAPI.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,8 @@ def get_user_data():
return {"error": f"User database file not found"}
except json.JSONDecodeError:
return {"error": "Error decoding JSON data"}

@app.get("/device_functions")
def get_device_functions():
"""Returns the list of device functions."""
return {"functions": dj.deviceFunctions()}
1 change: 1 addition & 0 deletions backend/users.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
selected_user = ""
3 changes: 3 additions & 0 deletions database/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
FIREBASE_PROJECT_ID=powerhouse-62f4d
FIREBASE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC+dm+isAoRCwvI\nzuymf7bLtZ0ig1YLLVpcEsG36wX2JjXuvCptgQ35VKUX1rE5t7ChIt7Rv/c8+Z3o\nr1fSCS9Xowq64pmX5MXzyI8WXsolg+zoSGxBo+puwSkqfnfo5ozRaUz7YjkosGIM\nX2+A3+jAf1bge6X3CMXerTer1dr9XqOtA+JQ01rkb47Hvx+ty2KrDuVMw/b0ERbv\nIa5P7e7fVklF7yFNEGdcZck5+X6Dc79otvH1WFg82to2Id42lnt2zvEZRRJdIugU\nDelt41LHGiQ8fZzrUMH2ztSPuDjSOwL1EMscZaIlULOl34uH4yIifnEC4N+X56l9\nmGbFQDvzAgMBAAECggEAXCmSVn5puJqIPsjGYcD+Eq/UlA1KdwkgptoarCUJUvVl\ndprNywQ+X05yvsjQJFY7HZXyjTufZv2AyEYEsGlqY7VM7WbWHNaCZTdA6fm/Ro0C\nd9oHrHGlcKd+hIYC/wuEy5ZxPllmwEYtg8AwhAOVLITR5Llgc1vm55wZCsjmeWBa\n/feT1HVPH550NZyGvnmokrnq74kpEKG/pEHB1cG9hkJOPwLoTbEIE3eHDBI2xSaI\nlGOBNEi21q8isYLXSjPCzHINBr+/59VbkJGMHBgIYUDLgAUdZbAzvL3PQTyPCrPH\nIrCU1lg/KOJNjnsFr1196thtaDyfjwppII02j+0SjQKBgQDiZD1XdDyUiBAb1Zdc\nILvojGRro3clSTZtTVpu+l60IldY4rrQ8uhBnpO0bQQflt0iq8N4IC6QsCAOzK6U\noH+6ZfgYL5AZJKU5D5h+lNdMO963X1M+7NmqiXJygYLrh2wSpXceIjJHh3HxoYn9\npu6J5zgtTbJ/AffNB5aYQZUl9wKBgQDXX0TjHkpGXxrSYgGgWtNu5VfNnbDluKKt\nbQ2s6BNC7H74IGMF4/CmkzJk+u3t4Kqy6fiLcdC11NNbMc1WjOft8X2laGQRxL+8\n6SwwpuAEtYffwv5R8XzhTzaxQ0QykOZHF0KoXU90U52FLhnO4CabD/FBNDTGwyrl\n81WWJItq5QKBgQCkNI6lkyKpnTMJ7Vd2tCt3VfU0eYFe4wZ+KyAP5LuyLkJqrzFP\ngxBhIBMESBo4i2Vj02/Y/oan3YezFFUAapWaboGPkVgUVJcelyhFj9HFcpttJ9Aq\nkQKkBkOjuDpseXIy6B9JFQahCSXlXATJnT3henstqPBx3MyOwxVA5Di5KwKBgHuc\nFD9+LQcCorRIx8IvHtaxDpnWfab7N3Lt+kFY2WU5sw3aGLtSvdX8+Zl3Y4N5xL73\nBk2SP0V9JulaRg38xUiNp519sqF4GMvl0BUEUeKiBZ/pnGmPfisaebt3bfbPrzry\nILHdDDMpomjDstBHWiD5H3Ba9Ed30HE2HdMUmmf5AoGAVXZD00+Z2MPZZXNmIoNt\nl2p5y+M/UCm8GP93287DaqVzeW1kpbwNmMPAgwUr0rrv+k/EpOUpgZbtCmttx3F5\nsTnX7nt3MwDTXUrtRMqVkahzWpf2KlgicQzbeH2CrtvdvLchAry0x3DQ0QAJWZ6O\nSCPTXOwvEU3x63TkwO+Ogbs=\n-----END PRIVATE KEY-----\n"
FIREBASE_CLIENT_EMAIL=firebase-adminsdk-fbsvc@powerhouse-62f4d.iam.gserviceaccount.com
176 changes: 124 additions & 52 deletions database/devices_db.py
Original file line number Diff line number Diff line change
@@ -1,63 +1,135 @@
import firebase_admin
from firebase_admin import credentials
from firebase_admin import firestore
import os
import time
import json
import os

import firebase_admin
from firebase_admin import credentials, firestore
import watchdog
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
from dotenv import load_dotenv
from hashlib import sha256

# Load environment variables
load_dotenv()

# Firebase credentials
FIREBASE_PROJECT_ID = os.getenv('FIREBASE_PROJECT_ID')
FIREBASE_PRIVATE_KEY = os.getenv('FIREBASE_PRIVATE_KEY').replace('\\n', '\n') # Fix newlines
FIREBASE_CLIENT_EMAIL = os.getenv('FIREBASE_CLIENT_EMAIL')

firebase_config = {
"type": "service_account",
"project_id": FIREBASE_PROJECT_ID,
"private_key": FIREBASE_PRIVATE_KEY,
"client_email": FIREBASE_CLIENT_EMAIL,
"token_uri": "https://oauth2.googleapis.com/token"
}

# Initialize Firebase Admin SDK if not already initialized
if not firebase_admin._apps:
cred = credentials.Certificate(firebase_config)
firebase_admin.initialize_app(cred)

cred = credentials.Certificate("/Users/annerinjeri/Downloads/powerhouse-62f4d-firebase-adminsdk-fbsvc-9a2e946c98.json")
firebase_admin.initialize_app(cred)
# Firestore reference
db = firestore.client()

load_dotenv()
# Path to the JSON file
JSON_FILE_PATH = os.path.join(os.path.dirname(__file__), "../backend/devices.json")

# Track last JSON hash to avoid unnecessary updates
last_json_hash = None


def hash_file_content(file_path):
"""Calculate SHA256 hash of the file content."""
with open(file_path, "r") as f:
content = f.read()
return sha256(content.encode()).hexdigest()


def update_firestore():
"""Sync JSON data with Firestore."""
global last_json_hash

new_hash = hash_file_content(JSON_FILE_PATH)
if new_hash == last_json_hash:
print("No changes detected in JSON. Skipping Firestore update.")
return # Skip update if the content hasn't changed

LAST_MODIFIED_TIME = 0
last_json_hash = new_hash # Update hash

def set_doc_data(coll_ref, file):
rel_path=rel_path = os.path.join(os.path.dirname(__file__), f"../backend/{file}")
JSONfile = open(rel_path, "r")
data = json.load(JSONfile)
JSONfile.close()
with open(JSON_FILE_PATH, "r") as f:
data = json.load(f)

# Filter devices that are "connected"
filtered_devices = [
{key: device[key] for key in ["id", "name", "ip"]}
for device in data["smart_home_devices"]

if device.get("connection_status") == "connected"
{key: device[key] for key in ["id", "name", "ip"]}
for device in data["smart_home_devices"]
if device.get("connection_status") == "connected"
]

for i in range(len(filtered_devices)):
coll_ref.document().set(filtered_devices[i])

def delete_collection(coll_ref):

count_query = coll_ref.count()
query_result = count_query.get()
coll_size=query_result[0][0].value

if coll_size == 0:
return

docs = coll_ref.list_documents(page_size=coll_size)
deleted = 0

for doc in docs:
print(f"Deleting doc {doc.id} => {doc.get().to_dict()}")
doc.delete()
deleted = deleted + 1

if deleted >= coll_size:
return delete_collection(coll_ref)

def is_json_updated(file):
global LAST_MODIFIED_TIME
current_modified_time = os.path.getmtime(file)
if current_modified_time > LAST_MODIFIED_TIME:
LAST_MODIFIED_TIME = current_modified_time
return True
return False


delete_collection(db.collection("Devices"))
set_doc_data(db.collection("Devices"),"devices.json")
existing_devices = {doc.id: doc.to_dict() for doc in db.collection("Devices").stream()}

added_devices = []
modified_devices = []
deleted_devices = []

new_device_ids = {str(device["id"]) for device in filtered_devices}

# Add or update devices
for device in filtered_devices:
device_id = str(device["id"])
if device_id not in existing_devices:
db.collection("Devices").document(device_id).set(device)
added_devices.append(device)
elif existing_devices[device_id] != device:
db.collection("Devices").document(device_id).set(device)
modified_devices.append(device)

# Delete devices not in the JSON anymore
for device_id in existing_devices.keys():
if device_id not in new_device_ids:
db.collection("Devices").document(device_id).delete()
deleted_devices.append(device_id)

# Logging updates
if added_devices:
print(f"Added devices: {added_devices}")
if modified_devices:
print(f"Updated devices: {modified_devices}")
if deleted_devices:
print(f"Deleted devices: {deleted_devices}")

if not (added_devices or modified_devices or deleted_devices):
print("No changes detected in Firestore.")


class JSONFileHandler(FileSystemEventHandler):
"""Watches the JSON file for modifications."""

def on_modified(self, event):
"""Triggered when JSON file is modified."""
if event.src_path.endswith("devices.json"):
print("Detected JSON file update. Syncing with Firestore...")
update_firestore()


def start_file_watcher():
"""Start watching the JSON file for changes."""
event_handler = JSONFileHandler()
observer = Observer()
observer.schedule(event_handler, path=os.path.dirname(JSON_FILE_PATH), recursive=False)
observer.start()

try:
while True:
time.sleep(1) # Keep script running
except KeyboardInterrupt:
observer.stop()
observer.join()


if __name__ == "__main__":
print("Starting file watcher for devices.json...")
start_file_watcher()

25 changes: 14 additions & 11 deletions database/users_db.json
Original file line number Diff line number Diff line change
@@ -1,35 +1,38 @@
{
"users": [
{
"user_id": 1,
"user_name": "John Doe",
"user_password": "1415",
"allocated_devices": [
"device1",
"device2",
"device3",
"device4",
"device5",
"device6"
"1",
"2",
"3",
"4",
"5",
"6"
],
"user_role": "super_user"
},
{
"user_id": 2,
"user_name": "Jane Doe",
"user_password": "2005",
"allocated_devices": [
"device3",
"device4"
"3",
"4"
],
"user_role": "sub_user"
},
{
"user_id": 3,
"user_name": "John Smith",
"user_password": "0007",
"allocated_devices": [
"device5",
"device6"
"5",
"6"
],
"user_role": "sub_user"
}
]
}
}
34 changes: 34 additions & 0 deletions database/users_db.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import firebase_admin
from firebase_admin import credentials, firestore
import json
import os
#rom dotenv import load_dotenv

cred = credentials.Certificate("/Users/annerinjeri/Downloads/powerhouse-62f4d-firebase-adminsdk-fbsvc-9a2e946c98.json")
firebase_admin.initialize_app(cred)
db = firestore.client()

#load_dotenv()
def_set_doc_data(collref,)
# Load JSON file
with open("users.json", "r") as file:
data = json.load(file)

# Upload users to Firestore with device references
for user in data["users"]:
user_id = user["user_name"].replace(" ", "_").lower() # Unique Firestore ID
user_ref = db.collection("Users").document(user_id)

# Convert device IDs into Firestore document references
device_refs = [db.collection("Devices").document(device_id) for device_id in user["allocated_devices"]]

user_data = {
"user_name": user["user_name"],
"user_password": user["user_password"], # Password stored as plain text (⚠ Not recommended for production)
"allocated_devices": device_refs, # Store Firestore document references
"user_role": user["user_role"]
}

user_ref.set(user_data)
print(f"User {user['user_name']} added with device references!")

Loading
Loading