This commit is contained in:
ferdzo
2024-10-10 12:58:04 +02:00
parent 48a4967e30
commit 56935db4d5
9 changed files with 519 additions and 145 deletions

View File

@@ -1,26 +1,14 @@
from django import forms from django import forms
from .models import Device, Sensor, SensorType from .models import Device, Sensor, SensorType
class DeviceForm(forms.ModelForm):
# Optionally include sensors as choices in the form if relevant
sensors = forms.ModelMultipleChoiceField(
queryset=Sensor.objects.all(),
required=False,
widget=forms.CheckboxSelectMultiple,
label='Sensors'
)
class DeviceForm(forms.ModelForm):
class Meta: class Meta:
model = Device model = Device
fields = ['name', 'ip', 'protocol'] fields = ['name', 'ip', 'protocol'] # Exclude sensors from the fields
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
# Optionally pass initial sensors for editing an existing device # No need to handle sensors in the form
if 'instance' in kwargs:
initial_sensors = kwargs['instance'].sensors.all() if kwargs['instance'] else None
initial = kwargs.get('initial', {})
initial['sensors'] = initial_sensors
kwargs['initial'] = initial
super(DeviceForm, self).__init__(*args, **kwargs) super(DeviceForm, self).__init__(*args, **kwargs)
def save(self, commit=True): def save(self, commit=True):
@@ -29,7 +17,6 @@ class DeviceForm(forms.ModelForm):
if commit: if commit:
device.save() device.save()
self.save_m2m() # Ensure M2M save happens
return device return device
class SensorWithTypeForm(forms.ModelForm): class SensorWithTypeForm(forms.ModelForm):
@@ -45,7 +32,11 @@ class SensorWithTypeForm(forms.ModelForm):
class Meta: class Meta:
model = Sensor model = Sensor
fields = ['device', 'enabled'] fields = ['enabled'] # Exclude 'device' from the form fields
def __init__(self, *args, **kwargs):
self.device = kwargs.pop('device', None) # Get the device from kwargs
super(SensorWithTypeForm, self).__init__(*args, **kwargs)
def save(self, commit=True): def save(self, commit=True):
# Create or get the SensorType # Create or get the SensorType
@@ -65,6 +56,7 @@ class SensorWithTypeForm(forms.ModelForm):
# Create Sensor with the SensorType found or created # Create Sensor with the SensorType found or created
sensor = super(SensorWithTypeForm, self).save(commit=False) sensor = super(SensorWithTypeForm, self).save(commit=False)
sensor.type = sensor_type sensor.type = sensor_type
sensor.device = self.device # Associate the sensor with the device
if commit: if commit:
sensor.save() sensor.save()

View File

@@ -11,6 +11,7 @@ from .models import Device, Sensor, SensorType
# Initialize Redis client # Initialize Redis client
redis_client = redis.StrictRedis(host='10.10.0.1', port=6379, db=0) redis_client = redis.StrictRedis(host='10.10.0.1', port=6379, db=0)
def devices_to_redis(): def devices_to_redis():
"""Fetch devices and their sensors' topics from Django and store them in Redis.""" """Fetch devices and their sensors' topics from Django and store them in Redis."""
devices = Device.objects.all() devices = Device.objects.all()
@@ -26,6 +27,7 @@ def devices_to_redis():
redis_client.set('mqtt_devices', json.dumps(devices_list)) redis_client.set('mqtt_devices', json.dumps(devices_list))
print("Devices with sensors stored in Redis.") print("Devices with sensors stored in Redis.")
def fetch_data_http(device, sensor): def fetch_data_http(device, sensor):
"""Fetch data from an HTTP sensor.""" """Fetch data from an HTTP sensor."""
sensor_type_name = sensor.type.name.lower() sensor_type_name = sensor.type.name.lower()
@@ -47,6 +49,7 @@ def fetch_data_http(device, sensor):
print(f"HTTP request failed for {device.name}: {e}") print(f"HTTP request failed for {device.name}: {e}")
return None return None
def fetch_data_mqtt(device, sensor): def fetch_data_mqtt(device, sensor):
"""Fetch data from Redis for a specific MQTT device and sensor.""" """Fetch data from Redis for a specific MQTT device and sensor."""
# Get the data for the specific device from Redis # Get the data for the specific device from Redis
@@ -70,9 +73,10 @@ def fetch_data_mqtt(device, sensor):
def is_recent_data(timestamp): def is_recent_data(timestamp):
"""Check if data is within a 2-minute freshness window.""" """Check if data is within a 1-minute freshness window."""
data_time = datetime.datetime.fromisoformat(timestamp) data_time = datetime.datetime.fromisoformat(timestamp)
return data_time > datetime.datetime.utcnow() - datetime.timedelta(minutes=2) return data_time > datetime.datetime.utcnow() - datetime.timedelta(minutes=1)
def insert_data(data, sensor_type): def insert_data(data, sensor_type):
"""Insert parsed data into the PostgreSQL database.""" """Insert parsed data into the PostgreSQL database."""
@@ -105,6 +109,7 @@ def insert_data(data, sensor_type):
except Exception as e: except Exception as e:
print(f"Failed to insert data: {e}") print(f"Failed to insert data: {e}")
@periodic_task(crontab(minute='*/1')) @periodic_task(crontab(minute='*/1'))
def fetch_data_from_all_devices(): def fetch_data_from_all_devices():
"""Fetch and insert data for all devices based on their protocol.""" """Fetch and insert data for all devices based on their protocol."""
@@ -123,6 +128,7 @@ def fetch_data_from_all_devices():
else: else:
print(f"No recent or valid data for {device.name}. Skipping.") print(f"No recent or valid data for {device.name}. Skipping.")
@periodic_task(crontab(minute='*/5')) @periodic_task(crontab(minute='*/5'))
def last_5_minutes(): def last_5_minutes():
"""Fetch the last 5 readings from TimescaleDB and store them in Redis.""" """Fetch the last 5 readings from TimescaleDB and store them in Redis."""
@@ -151,5 +157,6 @@ def last_5_minutes():
except Exception as e: except Exception as e:
print(f"Error fetching or storing the last 5 readings: {e}") print(f"Error fetching or storing the last 5 readings: {e}")
# Initialize device data in Redis # Initialize device data in Redis
devices_to_redis() devices_to_redis()

View File

@@ -3,142 +3,280 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>IoT Sensor Dashboard</title> <title>IoT Dashboard</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/css/bootstrap.min.css" rel="stylesheet"> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script> <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datetimepicker/4.17.47/css/bootstrap-datetimepicker.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datetimepicker/4.17.47/js/bootstrap-datetimepicker.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/raphael/2.3.0/raphael.min.js"></script> <!-- Include Raphael.js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/justgage/1.4.0/justgage.min.js"></script> <!-- Include JustGage.js -->
<style> <style>
.device-panel { .navbar {
margin-bottom: 20px;
}
.sensor-card {
margin-top: 20px;
}
.gpt-section {
margin-top: 20px;
background-color: #f8f9fa;
padding: 20px;
border-radius: 10px;
}
.chart-container {
position: relative;
height: 300px;
margin-bottom: 20px; /* Space between charts */
}
.footer {
margin-top: 40px;
padding: 20px;
background-color: #343a40;
color: white;
text-align: center;
}
.date-picker {
margin-bottom: 20px; margin-bottom: 20px;
} }
</style> </style>
</head> </head>
<body class="container my-4"> <body>
<!-- GPT Summary -->
<h4 class="text-center mb-4">{{ gpt }}</h4>
<h1 class="text-center mb-4">IoT Sensor Data Dashboard</h1> <!-- Navigation Bar -->
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<a class="navbar-brand" href="{% url 'index' %}">IoT Dashboard</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-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 ml-auto">
<li class="nav-item"><a class="nav-link" href="{% url 'device_list' %}">Devices</a></li>
<li class="nav-item"><a class="nav-link" href="{% url 'add_sensor_with_type' %}">Sensors</a></li>
<li class="nav-item"><a class="nav-link" href="/admin/">Logout</a></li>
</ul>
</div>
</nav>
<!-- Button to add a new "island" (device panel) --> <!-- Page Title -->
<div class="text-center mb-4"> <div class="container text-center">
<button class="btn btn-success" onclick="addDevicePanel()">Add Device Panel</button> <h2>GPT Summary from All the Environment's Sensors</h2>
</div> </div>
<!-- Container for dynamic device panels ("islands") --> <!-- GPT Recommendations Section -->
<div id="device-panels" class="row"> <div class="container gpt-section">
<!-- Dynamic panels will be added here --> <div class="row">
<div class="col-md-8">
<h5>Modular Sensor Graphs with Current Parameters</h5>
<!-- Date Pickers for selecting start and end dates -->
<div class="row date-picker">
<div class="col-md-5">
<label for="startDate">Start Date and Time:</label>
<input type="text" id="startDate" class="form-control datetimepicker-input" data-toggle="datetimepicker" data-target="#startDate"/>
</div>
<div class="col-md-5">
<label for="endDate">End Date and Time:</label>
<input type="text" id="endDate" class="form-control datetimepicker-input" data-toggle="datetimepicker" data-target="#endDate"/>
</div>
<div class="col-md-2">
<label>&nbsp;</label>
<button id="applyDateRange" class="btn btn-primary btn-block">Apply</button>
</div>
</div>
<!-- Sensor Chart -->
<div id="sensor-charts" class="chart-container">
<canvas id="sensorChart"></canvas>
</div>
<!-- Button to add a new chart -->
<button id="addChartBtn" class="btn btn-success">Add New Chart</button>
</div>
<div class="col-md-4">
<h5>GPT Recommendations for Parameters</h5>
<p id="gpt-summary"></p>
</div>
</div>
</div>
<!-- Device Cards Section -->
<div class="container sensor-card">
<div class="row" id="device-list">
<!-- Device cards will be dynamically inserted here -->
</div>
</div>
<!-- Footer -->
<div class="footer">
<p>&copy; 2024 IoT Dashboard. All rights reserved.</p>
</div> </div>
<script> <script>
const devices = {{ devices_json|safe }}; $(document).ready(function() {
let panelCount = 0; // Initialize the date pickers
const charts = {}; $('#startDate').datetimepicker({ format: 'YYYY-MM-DD HH:mm:ss' });
$('#endDate').datetimepicker({ format: 'YYYY-MM-DD HH:mm:ss' });
// Function to add a new device panel // Load device cards
function addDevicePanel() { var devices = JSON.parse('{{ devices_json | safe }}');
const panelId = `device-panel-${panelCount}`;
const chartId = `sensorChart-${panelCount}`;
const panelHtml = `
<div class="col-md-6 device-panel" id="${panelId}">
<div class="card">
<div class="card-header d-flex justify-content-between">
<span>Device Panel</span>
<button class="btn btn-danger btn-sm" onclick="removeDevicePanel('${panelId}')">Remove</button>
</div>
<div class="card-body">
<div class="mb-3">
<label for="device-select-${panelId}" class="form-label">Choose a device:</label>
<select id="device-select-${panelId}" class="form-select" onchange="fetchSensorData('${panelId}', '${chartId}')">
<!-- Dynamically populated options -->
</select>
</div>
<!-- Date and Time Selection -->
<div class="mb-3">
<label for="start-date-${panelId}" class="form-label">Start Date and Time:</label>
<input type="datetime-local" id="start-date-${panelId}" class="form-control">
</div>
<div class="mb-3">
<label for="end-date-${panelId}" class="form-label">End Date and Time:</label>
<input type="datetime-local" id="end-date-${panelId}" class="form-control">
</div>
<button class="btn btn-primary w-100" onclick="fetchSensorData('${panelId}', '${chartId}')">Fetch Data</button>
<canvas id="${chartId}" class="mt-4"></canvas>
</div>
</div>
</div>`;
document.getElementById('device-panels').insertAdjacentHTML('beforeend', panelHtml);
populateDeviceSelect(`device-select-${panelId}`);
const ctx = document.getElementById(chartId).getContext('2d');
charts[chartId] = new Chart(ctx, {
type: 'line',
data: { labels: [], datasets: [] },
options: { responsive: true }
});
panelCount++; // Increment panelCount after using it
}
// Function to populate device dropdown
function populateDeviceSelect(selectId) {
const deviceSelect = document.getElementById(selectId);
deviceSelect.innerHTML = ""; // Clear previous options
devices.forEach(device => { devices.forEach(device => {
const option = document.createElement('option'); $('#device-list').append(`
option.value = device.name; <div class="col-md-4">
option.text = `${device.name} (${device.sensors__type__name})`; <div class="card">
deviceSelect.appendChild(option); <div class="card-body">
<h5 class="card-title">${device.name}</h5>
<p class="card-text">Sensor Type: ${device.sensors__type__name}</p>
</div>
</div>
</div>
`);
});
// GPT Summary
var gptData = {{ gpt | safe }}; // Assuming the GPT data is passed from the server
$('#gpt-summary').text(gptData.summary);
// Update recommendations
function updateRecommendations() {
const recommendations = gptData.recommendations;
Object.keys(recommendations).forEach(key => {
$('#gpt-summary').append(`<br>${key}: ${recommendations[key]}`);
}); });
} }
updateRecommendations();
// Function to remove a device panel // Fetch sensor data and update the chart dynamically
function removeDevicePanel(panelId) { function updateChart(deviceName, startDate, endDate) {
const panel = document.getElementById(panelId); $.ajax({
panel.remove(); url: "{% url 'fetch_device_data' %}",
} data: {
device: deviceName,
start_date: startDate,
end_date: endDate
},
success: function(response) {
// Clear any existing chart data
sensorChart.data.labels = [];
sensorChart.data.datasets = [];
// Function to fetch sensor data and update the chart for a specific panel // Loop through the response to extract times and values for each metric
function fetchSensorData(panelId, chartId) { for (var metric in response) {
const device = document.getElementById(`device-select-${panelId}`).value; sensorChart.data.labels = response[metric].times;
const startDate = document.getElementById(`start-date-${panelId}`).value; sensorChart.data.datasets.push({
const endDate = document.getElementById(`end-date-${panelId}`).value;
if (!device || !startDate || !endDate) {
alert('Please select a device and both start and end date/time.');
return;
}
fetch(`/fetch_device_data?device=${device}&start_date=${startDate}&end_date=${endDate}`)
.then(response => response.json())
.then(data => {
const times = Object.values(data)[0].times;
const datasets = Object.keys(data).map(metric => ({
label: metric, label: metric,
data: data[metric].values, data: response[metric].values,
borderColor: getRandomColor(), borderColor: 'rgba(75, 192, 192, 1)',
fill: false fill: false
}));
charts[chartId].data.labels = times;
charts[chartId].data.datasets = datasets;
charts[chartId].update();
}); });
} }
// Function to generate random color for chart lines // Update the chart with the new data
function getRandomColor() { sensorChart.update();
const letters = '0123456789ABCDEF'; },
let color = '#'; error: function(xhr, status, error) {
for (let i = 0; i < 6; i++) color += letters[Math.floor(Math.random() * 16)]; console.error("Failed to fetch sensor data:", error);
return color; }
});
} }
// Initialize with one device panel // Initialize Chart.js with empty data
addDevicePanel(); var ctx = document.getElementById('sensorChart').getContext('2d');
var sensorChart = new Chart(ctx, {
type: 'line',
data: {
labels: [], // Labels will be dynamically loaded
datasets: [] // Datasets will be dynamically loaded
},
options: {
responsive: true,
title: {
display: true,
text: 'Sensor Data Over Time'
},
scales: {
x: {
display: true,
title: {
display: true,
text: 'Time'
}
},
y: {
display: true,
title: {
display: true,
text: 'Value'
}
}
}
}
});
// Function to apply date range and fetch new data
$('#applyDateRange').click(function() {
var startDate = $('#startDate').val();
var endDate = $('#endDate').val();
var deviceName = 'Livingroom'; // Default device for now, update this as needed
// Call the updateChart function with the selected dates
updateChart(deviceName, startDate, endDate);
});
// Fetch initial data for default device
updateChart('Livingroom', null, null);
// Function to dynamically add a new chart
$('#addChartBtn').click(function() {
var newChartId = 'chart' + Date.now(); // Unique ID for each chart
addNewChart(newChartId, {
labels: ['Jan', 'Feb', 'Mar', 'Apr'],
datasets: [{
label: 'New Sensor Data',
data: [30, 60, 90, 120],
borderColor: 'rgba(75, 192, 192, 1)',
fill: false
}]
});
});
// Function to dynamically add a new chart
function addNewChart(chartId, chartData) {
var canvas = document.createElement('canvas');
canvas.id = chartId;
document.getElementById('sensor-charts').appendChild(canvas);
var ctx = document.getElementById(chartId).getContext('2d');
new Chart(ctx, {
type: 'line',
data: chartData,
options: {
responsive: true,
title: {
display: true,
text: 'Dynamic Sensor Data Over Time'
},
scales: {
x: {
display: true,
title: {
display: true,
text: 'Time'
}
},
y: {
display: true,
title: {
display: true,
text: 'Value'
}
}
}
}
});
}
});
</script> </script>
</body> </body>
</html> </html>

View File

@@ -46,7 +46,9 @@
{% csrf_token %} {% csrf_token %}
<div class="mb-3"> <div class="mb-3">
<!-- Display the form fields --> <!-- Display the form fields -->
{{ form.as_p }} {{ form.name.label_tag }} {{ form.name }}<br>
{{ form.ip.label_tag }} {{ form.ip }}<br>
{{ form.protocol.label_tag }} {{ form.protocol }}<br>
<!-- If there are errors, display them --> <!-- If there are errors, display them -->
{% if form.errors %} {% if form.errors %}
@@ -64,6 +66,14 @@
<button type="submit" class="btn btn-success">Save</button> <button type="submit" class="btn btn-success">Save</button>
<a href="{% url 'device_list' %}" class="btn btn-secondary">Cancel</a> <a href="{% url 'device_list' %}" class="btn btn-secondary">Cancel</a>
</form> </form>
<!-- Links for adding/editing sensors for this specific device -->
<div class="mt-4">
{% if form.instance.pk %}
<a href="{% url 'add_sensor' form.instance.pk %}" class="btn btn-primary">Add Sensor</a>
<a href="{% url 'sensor_list' form.instance.pk %}" class="btn btn-info">Edit Sensors</a>
{% endif %}
</div>
</div> </div>
</body> </body>
</html> </html>

View File

@@ -0,0 +1,48 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Confirm Delete Sensor</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body class="bg-light">
<!-- Navbar -->
<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 'index' %}">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>
<!-- Delete Confirmation -->
<div class="container mt-5">
<h1 class="text-center mb-4">Confirm Delete Sensor</h1>
<div class="alert alert-warning">
<strong>Warning!</strong> Are you sure you want to delete the sensor "{{ sensor.type.name }}"? This action cannot be undone.
</div>
<form method="POST">
{% csrf_token %}
<div class="text-center">
<button type="submit" class="btn btn-danger">Delete</button>
<a href="{% url 'sensor_list' sensor.device.pk %}" class="btn btn-secondary">Cancel</a>
</div>
</form>
</div>
</body>
</html>

View File

@@ -23,6 +23,7 @@
<form method="post"> <form method="post">
{% csrf_token %} {% csrf_token %}
{{ form.as_p }} <!-- Renders the inputs for all fields you added in your form --> {{ form.as_p }} <!-- Renders the inputs for all fields you added in your form -->
<input type="hidden" name="device_id" value="{{ device.id }}"> <!-- Hidden field for device ID -->
<button type="submit" class="btn btn-success">Save</button> <button type="submit" class="btn btn-success">Save</button>
<a href="{% url 'device_list' %}" class="btn btn-secondary">Cancel</a> <a href="{% url 'device_list' %}" class="btn btn-secondary">Cancel</a>
</form> </form>

View File

@@ -0,0 +1,64 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Manage Sensors for {{ device.name }}</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body class="bg-light">
<!-- Navbar -->
<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 'index' %}">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>
<!-- Sensor Management -->
<div class="container mt-5">
<h1>Sensors for {{ device.name }}</h1>
<table class="table">
<thead>
<tr>
<th>Sensor Type</th>
<th>Enabled</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for sensor in sensors %}
<tr>
<td>{{ sensor.type.name }}</td>
<td>{{ sensor.enabled }}</td>
<td>
<a href="{% url 'edit_sensor' sensor.pk %}" class="btn btn-warning btn-sm">Edit</a>
<a href="{% url 'delete_sensor' sensor.pk %}" class="btn btn-danger btn-sm">Delete</a>
</td>
</tr>
{% empty %}
<tr>
<td colspan="3">No sensors found for this device.</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</body>
</html>

View File

@@ -28,5 +28,10 @@ urlpatterns = [
path('devices/edit/<int:pk>/', views.edit_device, name='edit_device'), path('devices/edit/<int:pk>/', views.edit_device, name='edit_device'),
path('devices/delete/<int:pk>/', views.delete_device, name='delete_device'), path('devices/delete/<int:pk>/', views.delete_device, name='delete_device'),
path('logout/', views.logout_view, name='logout'), path('logout/', views.logout_view, name='logout'),
path('sensor/add/',views.add_sensor_with_type,name="add_sensor_with_type") path('sensors/<int:device_id>/', views.sensor_list, name='sensor_list'),
path('sensor/add/', views.add_sensor_with_type, name="add_sensor_with_type"),
# path('devices/<int:device_id>/sensors/', views.sensor_list, name='sensor_list'),
path('device/<int:device_id>/add_sensor/', views.add_sensor, name='add_sensor'),
path('sensor/edit/<int:pk>/', views.edit_sensor, name='edit_sensor'),
path('sensor/delete/<int:pk>/', views.delete_sensor, name='delete_sensor'),
] ]

View File

@@ -1,14 +1,13 @@
import json import json
from django.core.serializers.json import DjangoJSONEncoder
from django.http import JsonResponse, HttpResponse
from django.db import connections
from django.shortcuts import render, redirect, get_object_or_404
from django.views.decorators.http import require_GET
from .models import Device, Sensor, SensorType
from .forms import DeviceForm, SensorWithTypeForm
import redis import redis
from django.core.serializers.json import DjangoJSONEncoder
from django.db import connections
from django.http import JsonResponse, HttpResponse
from django.shortcuts import render, redirect, get_object_or_404
from .forms import DeviceForm, SensorWithTypeForm
from .models import Device, Sensor
redis_client = redis.StrictRedis(host='10.10.0.1', port=6379, db=0) redis_client = redis.StrictRedis(host='10.10.0.1', port=6379, db=0)
@@ -27,18 +26,78 @@ def chart(request):
# Pass devices data to the context # Pass devices data to the context
gpt = fetch_gpt_data() gpt = fetch_gpt_data()
gpt = json.loads(gpt) gpt = json.loads(gpt)
context = {'devices_json': devices_json, 'gpt': gpt["summary"]} context = {'devices_json': devices_json, 'gpt': gpt}
return render(request, 'chart.html', context) return render(request, 'chart.html', context)
# Fetch sensor data (AJAX) # Fetch sensor data (AJAX)
# def fetch_device_data(request):
# device_name = request.GET.get('device', 'Livingroom')
# start_date = request.GET.get('start_date')
# end_date = request.GET.get('end_date')
#
# # Log the parameters to ensure they are correct
# print("Device Name:", device_name)
# print("Start Date:", start_date)
# print("End Date:", end_date)
#
# # Get the specific device by name
# device = get_object_or_404(Device, name=device_name)
#
# # Initialize the results dictionary to store sensor data
# results = {}
#
# # Prepare SQL query and parameters for the specific sensor type
# query = """
# SELECT time, metric, value
# FROM sensor_readings
# WHERE device_name = %s
# """
# params = [device.name]
#
# # Add time filtering to the query
# if start_date:
# query += " AND time >= %s::timestamptz"
# params.append(start_date)
#
# if end_date:
# query += " AND time <= %s::timestamptz"
# params.append(end_date)
#
# # Log the final query and params
# print("Final Query:", query)
# print("Params Before Execution:", params)
#
# # Fetch data from the database
# with connections["data"].cursor() as cursor:
# cursor.execute(query, params)
# rows = cursor.fetchall()
#
# # Process the results and group them by sensor type (metric)
# for row in rows:
# time, metric, value = row
# formatted_time = time.strftime('%Y-%m-%d %H:%M:%S')
#
# if metric not in results:
# results[metric] = {
# 'times': [],
# 'values': []
# }
# results[metric]['times'].append(formatted_time)
# results[metric]['values'].append(value)
#
# return JsonResponse(results)
def fetch_device_data(request): def fetch_device_data(request):
device_name = request.GET.get('device', 'Livingroom') device_name = request.GET.get('device', 'Livingroom')
sensor_name = request.GET.get('sensor') # Optional parameter for a specific sensor
start_date = request.GET.get('start_date') start_date = request.GET.get('start_date')
end_date = request.GET.get('end_date') end_date = request.GET.get('end_date')
# Log the parameters to ensure they are correct # Log the parameters to ensure they are correct
print("Device Name:", device_name) print("Device Name:", device_name)
print("Sensor Name:", sensor_name) # Log sensor name
print("Start Date:", start_date) print("Start Date:", start_date)
print("End Date:", end_date) print("End Date:", end_date)
@@ -48,7 +107,7 @@ def fetch_device_data(request):
# Initialize the results dictionary to store sensor data # Initialize the results dictionary to store sensor data
results = {} results = {}
# Prepare SQL query and parameters for the specific sensor type # Prepare SQL query and parameters for the device
query = """ query = """
SELECT time, metric, value SELECT time, metric, value
FROM sensor_readings FROM sensor_readings
@@ -56,6 +115,11 @@ def fetch_device_data(request):
""" """
params = [device.name] params = [device.name]
# If a specific sensor is specified, filter by that sensor
if sensor_name:
query += " AND metric = %s"
params.append(sensor_name)
# Add time filtering to the query # Add time filtering to the query
if start_date: if start_date:
query += " AND time >= %s::timestamptz" query += " AND time >= %s::timestamptz"
@@ -89,6 +153,7 @@ def fetch_device_data(request):
return JsonResponse(results) return JsonResponse(results)
def index(request): def index(request):
if request.user.is_authenticated: if request.user.is_authenticated:
return redirect("/chart/") return redirect("/chart/")
@@ -130,6 +195,7 @@ def delete_device(request, pk):
return redirect('device_list') return redirect('device_list')
return render(request, 'device_confirm_delete.html', {'device': device}) return render(request, 'device_confirm_delete.html', {'device': device})
def add_sensor_with_type(request): def add_sensor_with_type(request):
if request.method == 'POST': if request.method == 'POST':
form = SensorWithTypeForm(request.POST) form = SensorWithTypeForm(request.POST)
@@ -142,10 +208,53 @@ def add_sensor_with_type(request):
context = {'form': form} context = {'form': form}
return render(request, 'sensor_form.html', context) return render(request, 'sensor_form.html', context)
def logout_view(request): def logout_view(request):
return redirect("/admin") return redirect("/admin")
def devices_api(request): def devices_api(request):
devices = list(Device.objects.all().values('name', 'sensors__type__name')) devices = list(Device.objects.all().values('name', 'sensors__type__name'))
return JsonResponse(devices, safe=False) return JsonResponse(devices, safe=False)
def sensor_list(request, device_id):
device = get_object_or_404(Device, id=device_id)
sensors = device.sensors.all() # Get sensors for this specific device
return render(request, 'sensor_list.html', {'device': device, 'sensors': sensors})
def edit_sensor(request, pk):
sensor = get_object_or_404(Sensor, pk=pk)
if request.method == 'POST':
form = SensorWithTypeForm(request.POST, instance=sensor)
if form.is_valid():
form.save()
return redirect('sensor_list', device_id=sensor.device.pk)
else:
form = SensorWithTypeForm(instance=sensor)
return render(request, 'sensor_form.html', {'form': form})
def delete_sensor(request, pk):
sensor = get_object_or_404(Sensor, pk=pk)
if request.method == 'POST':
device_id = sensor.device.pk
sensor.delete()
return redirect('sensor_list', device_id=device_id)
return render(request, 'sensor_confirm_delete.html', {'sensor': sensor})
def add_sensor(request, device_id):
device = get_object_or_404(Device, pk=device_id)
if request.method == 'POST':
form = SensorWithTypeForm(request.POST)
if form.is_valid():
sensor = form.save(commit=False)
sensor.device = device # Associate the sensor with the device
sensor.save()
return redirect('device_list') # Redirect to device list or appropriate view
else:
form = SensorWithTypeForm()
return render(request, 'sensor_form.html', {'form': form, 'device': device})