Big update

This commit is contained in:
ferdzo
2024-08-28 22:10:36 +02:00
parent 302071473f
commit 70d778a31e
16 changed files with 530 additions and 29 deletions

2
.gitignore vendored
View File

@@ -0,0 +1,2 @@
/iotDashboard/.env
/.idea

Binary file not shown.

BIN
demo.db

Binary file not shown.

6
iotDashboard/apps.py Normal file
View File

@@ -0,0 +1,6 @@
from django.apps import AppConfig
class IotDashboardConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'iotDashboard'

7
iotDashboard/forms.py Normal file
View File

@@ -0,0 +1,7 @@
from django import forms
from .models import Device
class DeviceForm(forms.ModelForm):
class Meta:
model = Device
fields = ['name', 'ip', 'protocol', 'temperature', 'humidity']

View File

@@ -1,6 +1,6 @@
import requests import requests
devices = {"livingroom":"192.168.1.56"} devices = {"esp1":"192.168.244.131"}
def getTemp(device): def getTemp(device):
r = requests.get("http://"+devices[device]+"/sensor/temperature") r = requests.get("http://"+devices[device]+"/sensor/temperature")

View File

@@ -1,17 +1,10 @@
from django.db import models from django.db import models
class Device(models.Model): class Device(models.Model):
name = models.CharField(max_length=50) name = models.CharField(max_length=50)
ip = models.CharField(max_length=20) ip = models.CharField(max_length=20)
protocol = models.CharField(max_length=20) protocol = models.CharField(max_length=20)
temperature = models.BooleanField(default=False) temperature = models.BooleanField(default=False)
humidity = models.BooleanField(default=False) humidity = models.BooleanField(default=False)
brightness = models.BooleanField(default=False)
def __str__(self):
return self.name
class Measurement(models.Model):
temperature = models.CharField(required=False,max_length=10)
humidity = models.CharField(required=False,max_length=10)
brightness = models.CharField(required=False,max_length=10)

View File

@@ -11,6 +11,9 @@ https://docs.djangoproject.com/en/4.2/ref/settings/
""" """
import environ import environ
from pathlib import Path from pathlib import Path
import os
from huey import SqliteHuey
# Build paths inside the project like this: BASE_DIR / 'subdir'. # Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent BASE_DIR = Path(__file__).resolve().parent.parent
@@ -24,6 +27,7 @@ environ.Env.read_env()
# SECURITY WARNING: keep the secret key used in production secret! # SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = env('SECRET_KEY') SECRET_KEY = env('SECRET_KEY')
CONNECTION_STRING = env('CONNECTION_STRING')
# SECURITY WARNING: don't run with debug turned on in production! # SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True DEBUG = True
@@ -40,6 +44,9 @@ INSTALLED_APPS = [
'django.contrib.sessions', 'django.contrib.sessions',
'django.contrib.messages', 'django.contrib.messages',
'django.contrib.staticfiles', 'django.contrib.staticfiles',
'iotDashboard',
'huey.contrib.djhuey',
] ]
MIDDLEWARE = [ MIDDLEWARE = [
@@ -57,7 +64,7 @@ ROOT_URLCONF = 'iotDashboard.urls'
TEMPLATES = [ TEMPLATES = [
{ {
'BACKEND': 'django.template.backends.django.DjangoTemplates', 'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [], 'DIRS': [os.path.join(BASE_DIR, 'iotDashboard/templates')],
'APP_DIRS': True, 'APP_DIRS': True,
'OPTIONS': { 'OPTIONS': {
'context_processors': [ 'context_processors': [
@@ -86,8 +93,8 @@ DATABASES = {
"NAME" : "example", "NAME" : "example",
"USER": "postgres", "USER": "postgres",
"PASSWORD": env('PASSWORD'), "PASSWORD": env('PASSWORD'),
"HOST": 'localhost', "HOST": '10.10.0.1',
"PORT": '5432', "PORT": '5555',
} }
} }
@@ -132,3 +139,13 @@ STATIC_URL = 'static/'
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field # https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
HUEY = {
'huey_class': 'huey.SqliteHuey', # Or 'huey.RedisHuey' for Redis
'filename': 'demo.db', # SQLite file for task storage
'results': True,
'store_none': False,
'immediate': False,
'utc': True,
}

37
iotDashboard/tasks.py Normal file
View File

@@ -0,0 +1,37 @@
import psycopg2
import requests
from huey import crontab
from huey.contrib.djhuey import periodic_task
from datetime import datetime
from django.conf import settings
from .models import Device # Import your Device model
# Fetch data from the device using REST API
def fetch_data_from_device(device):
data = dict()
data["time"] = datetime.now()
data["device"] = device.name # Use device name
r = requests.get(f"http://{device.ip}/sensor/tempreature")
data["temperature"] = r.json()['value']
r = requests.get(f"http://{device.ip}/sensor/humidity")
data["humidity"] = r.json()['value']
return (data["time"], data["device"], data["temperature"], data["humidity"])
# Insert data into the database
def insert_data(device):
data = fetch_data_from_device(device)
with psycopg2.connect(settings.CONNECTION_STRING) as conn: # Use Django's connection string
cursor = conn.cursor()
insert_query = """
INSERT INTO conditions (time, device, temperature, humidity)
VALUES (%s, %s, %s, %s)
"""
cursor.execute(insert_query, data)
conn.commit()
# Periodic task to fetch data from all devices every minute
@periodic_task(crontab(minute='*/1'))
def fetch_data_from_all_devices():
devices = Device.objects.all() # Fetch all devices from the database
for device in devices:
insert_data(device)

View File

@@ -0,0 +1,182 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Conditions Chart with Chart.js</title>
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
.chart-container {
position: relative;
width: 100%;
height: 400px;
background-color: #f9f9f9;
padding: 20px;
border-radius: 10px;
box-shadow: 0px 0px 20px rgba(0, 0, 0, 0.1);
}
#conditionsChart {
width: 100% !important;
height: 100% !important;
}
</style>
</head>
<body class="bg-light">
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="container-fluid">
<a class="navbar-brand" href="#">IoT Dashboard</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<a class="nav-link" href="{% url 'chart' %}">Chart</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'device_list' %}">Devices</a>
</li>
</ul>
<ul class="navbar-nav">
{% if user.is_authenticated %}
<li class="nav-item">
<a class="nav-link" href="{% url 'logout' %}">Logout</a>
</li>
{% else %}
<li class="nav-item">
<a class="nav-link" href="{% url 'login' %}">Login</a>
</li>
{% endif %}
</ul>
</div>
</div>
</nav>
<div class="container mt-5">
<h1 class="text-center mb-4">Temperature and Humidity Over Time</h1>
<!-- Device Selector Dropdown -->
<div class="row mb-4">
<div class="col-md-4 offset-md-4">
<select id="deviceSelector" class="form-select" onchange="fetchDeviceData()">
{% for device in devices %}
<option value="{{ device.name }}">{{ device.name }} ({{ device.ip }})</option>
{% endfor %}
</select>
</div>
</div>
<!-- Date Pickers for Time Window -->
<div class="row mb-4">
<div class="col-md-6 offset-md-3 d-flex justify-content-between">
<div>
<label for="startDate" class="form-label">Start Date:</label>
<input type="datetime-local" id="startDate" class="form-control" onchange="fetchDeviceData()">
</div>
<div>
<label for="endDate" class="form-label">End Date:</label>
<input type="datetime-local" id="endDate" class="form-control" onchange="fetchDeviceData()">
</div>
</div>
</div>
<!-- Chart Container -->
<div class="row">
<div class="col-md-8 offset-md-2">
<div class="chart-container">
<canvas id="conditionsChart"></canvas>
</div>
</div>
</div>
</div>
<script>
var ctx = document.getElementById('conditionsChart').getContext('2d');
var conditionsChart;
function fetchDeviceData() {
var device = document.getElementById('deviceSelector').value;
var startDate = document.getElementById('startDate').value;
var endDate = document.getElementById('endDate').value;
fetch(`/fetch_device_data/?device=${device}&start_date=${startDate}&end_date=${endDate}`)
.then(response => response.json())
.then(data => {
if (conditionsChart) {
conditionsChart.destroy(); // Destroy the old chart
}
conditionsChart = new Chart(ctx, {
type: 'line',
data: {
labels: data.times,
datasets: [
{
label: 'Temperature (°C)',
data: data.temperatures,
borderColor: 'red',
fill: false,
},
{
label: 'Humidity (%)',
data: data.humidities,
borderColor: 'blue',
fill: false,
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
x: {
title: {
display: true,
text: 'Time'
},
ticks: {
autoSkip: true,
maxRotation: 45,
minRotation: 45,
}
},
y: {
title: {
display: true,
text: 'Values'
},
beginAtZero: true
}
},
plugins: {
legend: {
display: true,
position: 'top',
labels: {
boxWidth: 20,
padding: 20,
}
},
tooltip: {
enabled: true,
}
}
}
});
});
}
// Initial load for the default device and time range
fetchDeviceData();
</script>
<!-- Bootstrap JS and dependencies (Optional) -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

View File

@@ -0,0 +1,51 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Delete Device</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body class="bg-light">
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="container-fluid">
<a class="navbar-brand" href="#">IoT Dashboard</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<a class="nav-link" href="{% url 'chart' %}">Chart</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'device_list' %}">Devices</a>
</li>
</ul>
<ul class="navbar-nav">
{% if user.is_authenticated %}
<li class="nav-item">
<a class="nav-link" href="{% url 'logout' %}">Logout</a>
</li>
{% else %}
<li class="nav-item">
<a class="nav-link" href="{% url 'login' %}">Login</a>
</li>
{% endif %}
</ul>
</div>
</div>
</nav>
<div class="container mt-5">
<h1 class="text-center mb-4">Delete Device</h1>
<p class="text-center">Are you sure you want to delete the device "{{ device.name }}"?</p>
<form method="post" class="text-center">
{% csrf_token %}
<button type="submit" class="btn btn-danger">Delete</button>
<a href="{% url 'device_list' %}" class="btn btn-secondary">Cancel</a>
</form>
</div>
</body>
</html>

View File

@@ -0,0 +1,52 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% if form.instance.pk %}Edit{% else %}Add{% endif %} Device</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body class="bg-light">
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="container-fluid">
<a class="navbar-brand" href="#">IoT Dashboard</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<a class="nav-link" href="{% url 'chart' %}">Chart</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'device_list' %}">Devices</a>
</li>
</ul>
<ul class="navbar-nav">
{% if user.is_authenticated %}
<li class="nav-item">
<a class="nav-link" href="{% url 'logout' %}">Logout</a>
</li>
{% else %}
<li class="nav-item">
<a class="nav-link" href="{% url 'login' %}">Login</a>
</li>
{% endif %}
</ul>
</div>
</div>
</nav>
<div class="container mt-5">
<h1 class="text-center mb-4">{% if form.instance.pk %}Edit{% else %}Add{% endif %} Device</h1>
<form method="post">
{% csrf_token %}
<div class="mb-3">
{{ form.as_p }}
</div>
<button type="submit" class="btn btn-success">Save</button>
<a href="{% url 'device_list' %}" class="btn btn-secondary">Cancel</a>
</form>
</div>
</body>
</html>

View File

@@ -0,0 +1,73 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Device Management</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body class="bg-light">
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="container-fluid">
<a class="navbar-brand" href="#">IoT Dashboard</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<a class="nav-link" href="{% url 'chart' %}">Chart</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'device_list' %}">Devices</a>
</li>
</ul>
<ul class="navbar-nav">
{% if user.is_authenticated %}
<li class="nav-item">
<a class="nav-link" href="{% url 'logout' %}">Logout</a>
</li>
{% else %}
<li class="nav-item">
<a class="nav-link" href="{% url 'login' %}">Login</a>
</li>
{% endif %}
</ul>
</div>
</div>
</nav>
<div class="container mt-5">
<h1 class="text-center mb-4">Manage Devices</h1>
<a href="{% url 'add_device' %}" class="btn btn-primary mb-3">Add Device</a>
<table class="table table-striped">
<thead>
<tr>
<th>Name</th>
<th>IP Address</th>
<th>Protocol</th>
<th>Temperature Monitoring</th>
<th>Humidity Monitoring</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for device in devices %}
<tr>
<td>{{ device.name }}</td>
<td>{{ device.ip }}</td>
<td>{{ device.protocol }}</td>
<td>{{ device.temperature|yesno:"Yes,No" }}</td>
<td>{{ device.humidity|yesno:"Yes,No" }}</td>
<td>
<a href="{% url 'edit_device' device.pk %}" class="btn btn-warning btn-sm">Edit</a>
<a href="{% url 'delete_device' device.pk %}" class="btn btn-danger btn-sm">Delete</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</body>
</html>

View File

@@ -20,5 +20,13 @@ from iotDashboard import views
urlpatterns = [ urlpatterns = [
path('admin/', admin.site.urls), path('admin/', admin.site.urls),
path('',views.index) path('',views.index),
path('fetch_device_data/', views.fetch_device_data, name='fetch_device_data'),
path('chart/',views.chart,name='chart'),
path('devices/', views.device_list, name='device_list'),
path('devices/add/', views.add_device, name='add_device'),
path('devices/edit/<int:pk>/', views.edit_device, name='edit_device'),
path('devices/delete/<int:pk>/', views.delete_device, name='delete_device'),
path('login/', views.login_view, name='login'),
path('logout/', views.logout_view, name='logout'),
] ]

View File

@@ -1,18 +1,86 @@
from django.http import HttpResponse from django.http import HttpResponse, request, JsonResponse
from django.db import connections from django.db import connections
from django.shortcuts import render, redirect, get_object_or_404
def my_custom_sql(): from .models import Device
with connections['data'].cursor() as cursor: from .forms import DeviceForm
# cursor.execute("SELECT * FROM conditions WHERE device='livingroom';"
cursor.execute("SELECT * FROM conditions WHERE time > NOW() - INTERVAL '50 days' ;")
row = cursor.fetchall()
keys = ("time","device","tempreature","humidity")
return row
def fetch_device_data(request):
device = request.GET.get('device', 'livingroom')
start_date = request.GET.get('start_date')
end_date = request.GET.get('end_date')
query = """
SELECT time, temperature, humidity
FROM conditions
WHERE device = %s
"""
params = [device]
if start_date:
query += " AND time >= %s"
params.append(start_date)
if end_date:
query += " AND time <= %s"
params.append(end_date)
with connections["data"].cursor() as cursor:
cursor.execute(query, params)
rows = cursor.fetchall()
times = [row[0].strftime('%Y-%m-%d %H:%M:%S') for row in rows]
temperatures = [row[1] for row in rows]
humidities = [row[2] for row in rows]
return JsonResponse({
'times': times,
'temperatures': temperatures,
'humidities': humidities,
})
def chart(request):
devices = Device.objects.all()
context = {'devices': devices}
return render(request, 'chart.html', context)
def index(request): def index(request):
if request.user.is_authenticated: if request.user.is_authenticated:
return HttpResponse(my_custom_sql()) return HttpResponse(chart())
return HttpResponse("NOT AUTHENTICATED!!!") return HttpResponse("NOT AUTHENTICATED!!!")
def device_list(request):
devices = Device.objects.all()
return render(request, 'device_list.html', {'devices': devices})
def add_device(request):
if request.method == 'POST':
form = DeviceForm(request.POST)
if form.is_valid():
form.save()
return redirect('device_list')
else:
form = DeviceForm()
return render(request, 'device_form.html', {'form': form})
def edit_device(request, pk):
device = get_object_or_404(Device, pk=pk)
if request.method == 'POST':
form = DeviceForm(request.POST, instance=device)
if form.is_valid():
form.save()
return redirect('device_list')
else:
form = DeviceForm(instance=device)
return render(request, 'device_form.html', {'form': form})
def delete_device(request, pk):
device = get_object_or_404(Device, pk=pk)
if request.method == 'POST':
device.delete()
return redirect('device_list')
return render(request, 'device_confirm_delete.html', {'device': device})
def login_view():
pass
def logout_view():
pass

View File

@@ -1,18 +1,23 @@
import dotenv
import psycopg2 import psycopg2
from psycopg2 import sql from psycopg2 import sql
from datetime import datetime from datetime import datetime
import requests import requests
from huey import SqliteHuey, crontab from huey import SqliteHuey, crontab
from dotenv import load_dotenv
from pathlib import Path
# Initialize scheduler # Initialize scheduler
huey = SqliteHuey(filename='demo.db') huey = SqliteHuey(filename='demo.db')
dotenv_path = Path("iotDashboard/.env")
load_dotenv(dotenv_path=dotenv_path)
CONNECTION = dotenv.dotenv_values(dotenv_path)["CONNECTION_STRING"]
# Database connection # Database connection
CONNECTION = "postgres://postgres:postgres*@localhost:5432/example"
conn = psycopg2.connect(CONNECTION) conn = psycopg2.connect(CONNECTION)
# Devices # Devices
devices = {"livingroom": "192.168.1.56","bedroom":"192.168.1.57"} devices = {"livingroom": "192.168.244.131"}
# Func for fetching data from device using REST API # Func for fetching data from device using REST API
@@ -20,7 +25,7 @@ def fetch_data_from_device(device):
data = dict() data = dict()
data["time"] = datetime.now() data["time"] = datetime.now()
data["device"] = device data["device"] = device
r = requests.get("http://" + devices[device] + "/sensor/temperature") r = requests.get("http://" + devices[device] + "/sensor/tempreature")
data["temperature"] = r.json()['value'] data["temperature"] = r.json()['value']
r = requests.get("http://" + devices[device] + "/sensor/humidity") r = requests.get("http://" + devices[device] + "/sensor/humidity")
data["humidity"] = r.json()['value'] data["humidity"] = r.json()['value']