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
devices = {"livingroom":"192.168.1.56"}
devices = {"esp1":"192.168.244.131"}
def getTemp(device):
r = requests.get("http://"+devices[device]+"/sensor/temperature")

View File

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

View File

@@ -11,6 +11,9 @@ https://docs.djangoproject.com/en/4.2/ref/settings/
"""
import environ
from pathlib import Path
import os
from huey import SqliteHuey
# Build paths inside the project like this: BASE_DIR / 'subdir'.
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!
SECRET_KEY = env('SECRET_KEY')
CONNECTION_STRING = env('CONNECTION_STRING')
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
@@ -40,6 +44,9 @@ INSTALLED_APPS = [
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'iotDashboard',
'huey.contrib.djhuey',
]
MIDDLEWARE = [
@@ -57,7 +64,7 @@ ROOT_URLCONF = 'iotDashboard.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'DIRS': [os.path.join(BASE_DIR, 'iotDashboard/templates')],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
@@ -86,8 +93,8 @@ DATABASES = {
"NAME" : "example",
"USER": "postgres",
"PASSWORD": env('PASSWORD'),
"HOST": 'localhost',
"PORT": '5432',
"HOST": '10.10.0.1',
"PORT": '5555',
}
}
@@ -132,3 +139,13 @@ STATIC_URL = 'static/'
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
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 = [
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
def my_custom_sql():
with connections['data'].cursor() as cursor:
# 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
from django.shortcuts import render, redirect, get_object_or_404
from .models import Device
from .forms import DeviceForm
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):
if request.user.is_authenticated:
return HttpResponse(my_custom_sql())
return HttpResponse("NOT AUTHENTICATED!!!")
return HttpResponse(chart())
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
from psycopg2 import sql
from datetime import datetime
import requests
from huey import SqliteHuey, crontab
from dotenv import load_dotenv
from pathlib import Path
# Initialize scheduler
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
CONNECTION = "postgres://postgres:postgres*@localhost:5432/example"
conn = psycopg2.connect(CONNECTION)
# 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
@@ -20,7 +25,7 @@ def fetch_data_from_device(device):
data = dict()
data["time"] = datetime.now()
data["device"] = device
r = requests.get("http://" + devices[device] + "/sensor/temperature")
r = requests.get("http://" + devices[device] + "/sensor/tempreature")
data["temperature"] = r.json()['value']
r = requests.get("http://" + devices[device] + "/sensor/humidity")
data["humidity"] = r.json()['value']