This commit is contained in:
ferdzo
2024-10-13 23:22:31 +02:00
parent 56935db4d5
commit a825d27170
3 changed files with 282 additions and 287 deletions

View File

@@ -1,282 +1,271 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<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 Dashboard</title> <title>Conditions Chart with Chart.js</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css"> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<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>
.navbar {
margin-bottom: 20px;
}
.sensor-card {
margin-top: 20px;
}
.gpt-section {
margin-top: 20px;
background-color: #f8f9fa;
padding: 20px;
border-radius: 10px;
}
.chart-container { .chart-container {
position: relative; position: relative;
height: 300px; width: 100%;
margin-bottom: 20px; /* Space between charts */ height: 400px;
} background-color: #f9f9f9;
.footer {
margin-top: 40px;
padding: 20px; padding: 20px;
background-color: #343a40; border-radius: 10px;
color: white; box-shadow: 0px 0px 20px rgba(0, 0, 0, 0.1);
text-align: center;
} }
.date-picker {
#conditionsChart {
width: 100% !important;
height: 100% !important;
}
.current-conditions {
text-align: center;
padding: 20px;
margin-bottom: 20px; margin-bottom: 20px;
background-color: #ffffff;
border-radius: 10px;
box-shadow: 0px 0px 15px rgba(0, 0, 0, 0.1);
}
.current-conditions h2 {
font-size: 36px;
margin: 10px 0;
}
.current-conditions .value {
font-size: 48px;
font-weight: bold;
margin-bottom: 10px;
} }
</style> </style>
</head> </head>
<body>
<!-- Navigation Bar --> <body class="bg-light">
<nav class="navbar navbar-expand-lg navbar-light bg-light"> <nav class="navbar navbar-expand-lg navbar-light bg-light">
<a class="navbar-brand" href="{% url 'index' %}">IoT Dashboard</a> <div class="container-fluid">
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation"> <a class="navbar-brand" href="#">IoT Dashboard</a>
<span class="navbar-toggler-icon"></span> <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
</button> <span class="navbar-toggler-icon"></span>
<div class="collapse navbar-collapse" id="navbarNav"> </button>
<ul class="navbar-nav ml-auto"> <div class="collapse navbar-collapse" id="navbarNav">
<li class="nav-item"><a class="nav-link" href="{% url 'device_list' %}">Devices</a></li> <ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item"><a class="nav-link" href="{% url 'add_sensor_with_type' %}">Sensors</a></li> <li class="nav-item">
<li class="nav-item"><a class="nav-link" href="/admin/">Logout</a></li> <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="/admin">Logout</a>
</li>
{% else %}
<li class="nav-item">
<a class="nav-link" href="/admin">Login</a>
</li>
{% endif %}
</ul>
</div>
</div>
</nav>
<div class="container mt-5">
<div class="current-conditions">
<h4>GPT Data:</h4>
<p id="gptSummary">{{ gpt.summary }}</p>
<div id="gptRecommendations">
<h5>Recommendations:</h5>
<ul>
{% for sensor_type, value in gpt.recommendations.items %}
<h6>{{ sensor_type }}: {{ value }}</h6>
{% endfor %}
</ul> </ul>
</div> </div>
</nav>
<!-- Page Title -->
<div class="container text-center">
<h2>GPT Summary from All the Environment's Sensors</h2>
</div>
<!-- GPT Recommendations Section -->
<div class="container gpt-section">
<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>
<div class="col-md-4">
<h5>GPT Recommendations for Parameters</h5>
<p id="gpt-summary"></p>
</div>
</div>
</div>
<!-- Device Cards Section --> <!-- Device Selector Dropdown -->
<div class="container sensor-card"> <div class="row mb-4">
<div class="row" id="device-list"> <div class="col-md-4 offset-md-4">
<!-- Device cards will be dynamically inserted here --> <select id="deviceSelector" class="form-select" onchange="updateSensorOptions()">
</div> <option value="">-- Select a Device --</option>
</div> <script>
const devices = JSON.parse('{{ devices_json|escapejs }}');
<!-- Footer --> const deviceSelector = document.getElementById('deviceSelector');
<div class="footer"> const sensorSelector = document.getElementById('sensorSelector');
<p>&copy; 2024 IoT Dashboard. All rights reserved.</p>
</div>
<script> // Populate device selector
$(document).ready(function() { devices.forEach(device => {
// Initialize the date pickers const option = document.createElement('option');
$('#startDate').datetimepicker({ format: 'YYYY-MM-DD HH:mm:ss' }); option.value = device.name; // Device name used for identification
$('#endDate').datetimepicker({ format: 'YYYY-MM-DD HH:mm:ss' }); option.textContent = device.name;
deviceSelector.appendChild(option);
// Load device cards
var devices = JSON.parse('{{ devices_json | safe }}');
devices.forEach(device => {
$('#device-list').append(`
<div class="col-md-4">
<div class="card">
<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();
// Fetch sensor data and update the chart dynamically
function updateChart(deviceName, startDate, endDate) {
$.ajax({
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 = [];
// Loop through the response to extract times and values for each metric
for (var metric in response) {
sensorChart.data.labels = response[metric].times;
sensorChart.data.datasets.push({
label: metric,
data: response[metric].values,
borderColor: 'rgba(75, 192, 192, 1)',
fill: false
}); });
} </script>
</select>
</div>
</div>
// Update the chart with the new data <!-- Sensor Selector Dropdown -->
sensorChart.update(); <div class="row mb-4">
}, <div class="col-md-4 offset-md-4">
error: function(xhr, status, error) { <select id="sensorSelector" class="form-select">
console.error("Failed to fetch sensor data:", error); <option value="">-- Select a Sensor --</option>
} </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">
</div>
<div>
<label for="endDate" class="form-label">End Date:</label>
<input type="datetime-local" id="endDate" class="form-control">
</div>
</div>
</div>
<!-- Fetch Button -->
<div class="row mb-4">
<div class="col-md-4 offset-md-4 text-center">
<button class="btn btn-primary" onclick="fetchDeviceData()">Fetch Data</button>
</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 updateSensorOptions() {
const selectedDeviceName = document.getElementById('deviceSelector').value;
// Find the selected device in the devices array
const selectedDevice = devices.find(device => device.name === selectedDeviceName);
// Clear the existing options in the sensorSelector
const sensorSelector = document.getElementById('sensorSelector');
sensorSelector.innerHTML = '<option value="">-- Select a Sensor --</option>';
if (selectedDevice) {
// Populate the sensor selector with the sensors for the selected device
selectedDevice.sensors.forEach(sensor => {
const option = document.createElement('option');
option.value = sensor.id; // Set the sensor ID as the value
option.textContent = sensor.type; // Set the sensor type as the display text
sensorSelector.appendChild(option);
});
}
} }
// Initialize Chart.js with empty data function fetchDeviceData() {
var ctx = document.getElementById('sensorChart').getContext('2d'); var device = document.getElementById('deviceSelector').value;
var sensorChart = new Chart(ctx, { var sensor = document.getElementById('sensorSelector').value; // Get selected sensor
type: 'line', var startDate = document.getElementById('startDate').value;
data: { var endDate = document.getElementById('endDate').value;
labels: [], // Labels will be dynamically loaded
datasets: [] // Datasets will be dynamically loaded if (!device || !sensor) {
}, alert("Please select both a device and a sensor.");
options: { return;
responsive: true, }
title: {
display: true, fetch(`/fetch_device_data/?device=${device}&sensor=${sensor}&start_date=${startDate}&end_date=${endDate}`)
text: 'Sensor Data Over Time' .then(response => {
}, if (!response.ok) {
scales: { throw new Error("Network response was not ok: " + response.statusText);
x: { }
display: true, return response.json();
title: { })
display: true, .then(data => {
text: 'Time' console.log("Fetched Data:", data); // Debugging point to check data structure
} if (!data.times || !data.values) {
}, throw new Error("Invalid data format: Missing 'times' or 'values'");
y: {
display: true,
title: {
display: true,
text: 'Value'
}
}
}
} }
});
// Function to apply date range and fetch new data if (conditionsChart) {
$('#applyDateRange').click(function() { conditionsChart.destroy(); // Destroy the old chart
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 conditionsChart = new Chart(ctx, {
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', type: 'line',
data: chartData, data: {
labels: data.times,
datasets: [
{
label: `${sensor} (°C or %)`,
data: data.values,
borderColor: sensor === 'Temperature' ? 'red' : 'blue',
fill: false,
}
]
},
options: { options: {
responsive: true, responsive: true,
title: { maintainAspectRatio: false,
display: true,
text: 'Dynamic Sensor Data Over Time'
},
scales: { scales: {
x: { x: {
display: true,
title: { title: {
display: true, display: true,
text: 'Time' text: 'Time'
},
ticks: {
autoSkip: true,
maxRotation: 45,
minRotation: 45,
} }
}, },
y: { y: {
display: true,
title: { title: {
display: true, display: true,
text: 'Value' text: 'Values'
},
beginAtZero: true
}
},
plugins: {
legend: {
display: true,
position: 'top',
labels: {
boxWidth: 20,
padding: 20,
} }
},
tooltip: {
enabled: true,
} }
} }
} }
}); });
} })
}); .catch(error => {
</script> console.error("Error fetching data:", error);
alert("An error occurred while fetching data. Please try again.");
});
}
</script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body> </body>
</html> </html>

View File

@@ -10,34 +10,17 @@
<body class="bg-light"> <body class="bg-light">
<nav class="navbar navbar-expand-lg navbar-light bg-light"> <nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="container-fluid"> <a class="navbar-brand" href="{% url 'index' %}">IoT Dashboard</a>
<a class="navbar-brand" href="#">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">
<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>
<span class="navbar-toggler-icon"></span> </button>
</button> <div class="collapse navbar-collapse" id="navbarNav">
<div class="collapse navbar-collapse" id="navbarNav"> <ul class="navbar-nav ml-auto">
<ul class="navbar-nav me-auto mb-2 mb-lg-0"> <li class="nav-item"><a class="nav-link" href="{% url 'device_list' %}">Devices</a></li>
<li class="nav-item"> <li class="nav-item"><a class="nav-link" href="/admin/">Logout</a></li>
<a class="nav-link" href="{% url 'index' %}">Chart</a> </ul>
</li> </div>
<li class="nav-item"> </nav>
<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"> <div class="container mt-5">
<h1 class="text-center mb-4">{% if form.instance.pk %}Edit{% else %}Add{% endif %} Device</h1> <h1 class="text-center mb-4">{% if form.instance.pk %}Edit{% else %}Add{% endif %} Device</h1>

View File

@@ -1,7 +1,4 @@
import json
import redis import redis
from django.core.serializers.json import DjangoJSONEncoder
from django.db import connections from django.db import connections
from django.http import JsonResponse, HttpResponse from django.http import JsonResponse, HttpResponse
from django.shortcuts import render, redirect, get_object_or_404 from django.shortcuts import render, redirect, get_object_or_404
@@ -13,20 +10,39 @@ redis_client = redis.StrictRedis(host='10.10.0.1', port=6379, db=0)
def fetch_gpt_data(): def fetch_gpt_data():
return redis_client.get("gpt").decode("utf-8").strip('b"').replace('\\"', '"').replace("\\n", "").replace("\\", "") return redis_client.get("gpt").decode("utf-8").strip('b"').replace('\\"', '"').replace("\\n", "").replace("\\","").replace("\\u00b0", "°")
import json # Add this at the top of the file if not already present
def chart(request): def chart(request):
# Fetch devices and their related sensors # Fetch devices and their related sensors
devices = list(Device.objects.all().values('name', 'sensors__type__name')) devices = Device.objects.prefetch_related('sensors__type').all() # Prefetch related sensors and their types
# Serialize data to JSON format # Create a list of devices and associated sensors
devices_json = json.dumps(devices, cls=DjangoJSONEncoder) devices_json = [
{
"name": device.name,
"sensors": [{"id": sensor.id, "type": sensor.type.name} for sensor in device.sensors.all()]
}
for device in devices
]
# Pass devices data to the context try:
gpt = fetch_gpt_data() # Fetch GPT data from Redis and parse it
gpt = json.loads(gpt) gpt_data = fetch_gpt_data()
context = {'devices_json': devices_json, 'gpt': gpt} gpt = json.loads(gpt_data)
except (redis.RedisError, json.JSONDecodeError) as e:
# Handle errors if fetching GPT data or JSON parsing fails
gpt = {"summary": "Error fetching data", "recommendations": {}}
print(f"Error fetching or parsing GPT data: {e}")
# Serialize devices_json to JSON string for use in the template
context = {
'devices_json': json.dumps(devices_json), # Convert to a JSON string
'gpt': gpt
}
return render(request, 'chart.html', context) return render(request, 'chart.html', context)
@@ -91,11 +107,13 @@ def chart(request):
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 sensor_name = request.GET.get('sensor') # This will be the actual sensor name
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
sensor_name = Sensor.objects.get(id=sensor_name).type.name
print("Device Name:", device_name) print("Device Name:", device_name)
print("Sensor Name:", sensor_name) # Log sensor name print("Sensor Name:", sensor_name) # Log sensor name
print("Start Date:", start_date) print("Start Date:", start_date)
@@ -104,8 +122,9 @@ def fetch_device_data(request):
# Get the specific device by name # Get the specific device by name
device = get_object_or_404(Device, name=device_name) device = get_object_or_404(Device, name=device_name)
# Initialize the results dictionary to store sensor data # Initialize lists to store times and values
results = {} times = []
values = []
# Prepare SQL query and parameters for the device # Prepare SQL query and parameters for the device
query = """ query = """
@@ -115,10 +134,10 @@ def fetch_device_data(request):
""" """
params = [device.name] params = [device.name]
# If a specific sensor is specified, filter by that sensor # If a specific sensor is specified, filter by that sensor name (converted to lowercase)
if sensor_name: if sensor_name:
query += " AND metric = %s" query += " AND metric = LOWER(%s)" # Convert to lowercase for comparison
params.append(sensor_name) params.append(sensor_name.lower()) # Convert sensor name to lowercase
# Add time filtering to the query # Add time filtering to the query
if start_date: if start_date:
@@ -138,20 +157,24 @@ def fetch_device_data(request):
cursor.execute(query, params) cursor.execute(query, params)
rows = cursor.fetchall() rows = cursor.fetchall()
# Process the results and group them by sensor type (metric) # Log the number of rows returned
print("Number of Rows Returned:", len(rows))
# Process the results and extract times and values
for row in rows: for row in rows:
time, metric, value = row time, metric, value = row
formatted_time = time.strftime('%Y-%m-%d %H:%M:%S') formatted_time = time.strftime('%Y-%m-%d %H:%M:%S')
if metric not in results: times.append(formatted_time)
results[metric] = { values.append(value)
'times': [],
'values': []
}
results[metric]['times'].append(formatted_time)
results[metric]['values'].append(value)
return JsonResponse(results) # If no data is found, return empty arrays
if not times and not values:
print("No data found for the specified device and sensor.")
return JsonResponse({'times': [], 'values': []})
# Return the response in the expected format
return JsonResponse({'times': times, 'values': values})
def index(request): def index(request):