Update chart.html, experimenting with MQTT.

This commit is contained in:
ferdzo
2024-10-10 00:10:21 +02:00
parent 5d37ec2396
commit ed7d33e87f
11 changed files with 620 additions and 349 deletions

View File

@@ -0,0 +1,54 @@
import psycopg2
from psycopg2 import sql
import os
from dotenv import load_dotenv
# Load environment variables
load_dotenv()
# Define your database connection parameters
DATABASE_NAME = os.getenv('DB_NAME', 'example')
USER = os.getenv('DB_USER', 'postgres')
PASSWORD = os.getenv('DB_PASSWORD', 'coolermaster')
HOST = os.getenv('DB_HOST', '10.10.0.1')
PORT = os.getenv('DB_PORT', '5555')
def create_sensor_readings_table():
"""Create the sensor_readings table if it does not exist."""
try:
# Establish connection to the database
conn = psycopg2.connect(
dbname=DATABASE_NAME,
user=USER,
password=PASSWORD,
host=HOST,
port=PORT
)
with conn.cursor() as cursor:
# SQL command to create the sensor_readings table
create_table_query = """
CREATE TABLE IF NOT EXISTS sensor_readings (
time TIMESTAMPTZ NOT NULL,
device_name VARCHAR(255) NOT NULL, -- Use device_name as a string
metric VARCHAR(50) NOT NULL, -- Type of sensor
value DOUBLE PRECISION NOT NULL, -- The sensor's value
PRIMARY KEY (time, device_name, metric) -- Composite primary key
);
"""
cursor.execute(create_table_query)
print("Table 'sensor_readings' created or already exists.")
# Commit changes
conn.commit()
except Exception as e:
print(f"Error during database operations: {e}")
finally:
if conn:
conn.close()
print("Database connection closed.")
if __name__ == "__main__":
create_sensor_readings_table()

View File

@@ -1,7 +1,72 @@
from django import forms
from .models import Device
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 Meta:
model = Device
fields = ['name', 'ip', 'protocol', 'temperature', 'humidity']
fields = ['name', 'ip', 'protocol']
def __init__(self, *args, **kwargs):
# Optionally pass initial sensors for editing an existing device
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)
def save(self, commit=True):
# Save the device instance
device = super(DeviceForm, self).save(commit=False)
if commit:
device.save()
self.save_m2m() # Ensure M2M save happens
return device
class SensorWithTypeForm(forms.ModelForm):
# Add fields for SensorType directly in the form
type_name = forms.CharField(max_length=50, label="Sensor Type Name")
unit = forms.CharField(max_length=20, label="Unit", required=False)
protocol = forms.ChoiceField(
choices=[('mqtt', 'MQTT'), ('http', 'HTTP')],
label="Protocol"
)
topic = forms.CharField(max_length=100, label="Topic", required=False)
endpoint = forms.CharField(max_length=100, label="Endpoint", required=False)
class Meta:
model = Sensor
fields = ['device', 'enabled']
def save(self, commit=True):
# Create or get the SensorType
try:
sensor_type = SensorType.objects.get(name=self.cleaned_data['type_name'])
except SensorType.DoesNotExist:
sensor_type = SensorType(
name=self.cleaned_data['type_name'],
unit=self.cleaned_data['unit'],
protocol=self.cleaned_data['protocol'],
topic=self.cleaned_data['topic'],
endpoint=self.cleaned_data['endpoint']
)
if commit:
sensor_type.save()
# Create Sensor with the SensorType found or created
sensor = super(SensorWithTypeForm, self).save(commit=False)
sensor.type = sensor_type
if commit:
sensor.save()
return sensor

View File

@@ -0,0 +1,41 @@
import json
import redis
from dotenv import load_dotenv
from openai import OpenAI
load_dotenv()
client = OpenAI()
redis_client = redis.StrictRedis(host='10.10.0.1', port=6379, db=0)
data = redis_client.get("last5").decode("utf-8")
def analysis(environment_data):
completion = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system",
"content": "You are an assistant that analyzes environmental data for an office working space and provides "
"concise numerical insights."},
{
"role": "user",
"content": f"Analyze the following environmental data. The goal is maintaining optimal working "
f"conditions in the office and peak working brain. Focus on any outliers or necessary adjustments. The data is as following: {environment_data}."
f"The output should be only the recommendations in numerical form with postitive and negative "
f"numbers and also provide small summary in a sentence or two of the current conditions and "
f"easily computable in json format. Be consistent with the + and - signs and the summary"
}
],
response_format={"type": "json_object"}
)
output = completion.choices[0].message.content
return output
output = analysis(data)
redis_client.set("gpt",json.dumps(output))
print(output)

View File

@@ -1,10 +1,26 @@
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)
class SensorType(models.Model):
name = models.CharField(max_length=50, unique=True) # Sensor name, e.g., "CO2", "Noise", etc.
unit = models.CharField(max_length=20) # Unit of measurement, e.g., "ppm", "dB", "lux"
protocol = models.CharField(max_length=20, choices=[('mqtt', 'MQTT'), ('http', 'HTTP')]) # Protocol for communication
topic = models.CharField(max_length=100, null=True, blank=True) # Topic for MQTT communication
endpoint = models.CharField(max_length=100, null=True, blank=True) # Endpoint for HTTP communication
def __str__(self):
return self.name
return f"{self.name} ({self.unit})"
class Device(models.Model):
name = models.CharField(max_length=50) # Device name
ip = models.CharField(max_length=20) # Device IP address
protocol = models.CharField(max_length=20, choices=[('mqtt', 'MQTT'), ('http', 'HTTP')])
def __str__(self):
return self.name
class Sensor(models.Model):
device = models.ForeignKey(Device, related_name='sensors', on_delete=models.CASCADE)
type = models.ForeignKey(SensorType, on_delete=models.CASCADE)
enabled = models.BooleanField(default=True)
def __str__(self):
return f"{self.type.name} Sensor on {self.device.name}"

View File

@@ -6,83 +6,104 @@ import paho.mqtt.client as mqtt
import redis
from dotenv import load_dotenv
# Load environment variables
load_dotenv()
# Set up Redis client
REDIS_HOST = os.getenv('REDIS_HOST', 'localhost')
redis_client = redis.StrictRedis(host=REDIS_HOST, port=6379, db=0)
print("Connected to Redis Server")
# MQTT broker address
MQTT_BROKER = os.getenv('MQTT_BROKER')
mqtt_data = {}
def get_devices():
"""" Simple method to get all devices """
# Get devices from Redis
devices_json = redis_client.get('devices')
def get_mqtt_devices():
"""Retrieve MQTT devices and sensor details from Redis."""
devices_json = redis_client.get('mqtt_devices')
if devices_json:
return json.loads(devices_json)
return []
def build_device_map():
"""Build a mapping of device endpoints to friendly names."""
devices = get_mqtt_devices()
return {device['topic'].split('/')[0]: device['device_name'] for device in
devices} # Assuming topic starts with device name
def on_message(client, userdata, msg):
"""" Simple method to handle messages """
topic = msg.topic.split('/')
device_name = topic[0]
sensor = topic[-2]
"""Handle incoming messages from MQTT broker."""
try:
# Parse the incoming message topic
topic_parts = msg.topic.split('/')
device_endpoint = topic_parts[0] # This is the actual endpoint name
sensor_type = topic_parts[2] # Assuming sensor type is in the third part
if device_name not in mqtt_data:
mqtt_data[device_name] = {"time": datetime.now(),
"device": device_name,
"temperature": None,
"humidity": None}
sensor_value = float(msg.payload.decode())
print(f"Received message from {device_endpoint}, sensor {sensor_type}: {sensor_value}")
if sensor == "tempreature":
mqtt_data[device_name]["temperature"] = float(msg.payload.decode())
elif sensor == "humidity":
mqtt_data[device_name]["humidity"] = float(msg.payload.decode())
# Build the device map to get the friendly device name
device_map = build_device_map()
device_name = device_map.get(device_endpoint, device_endpoint) # Fallback to endpoint if not found
if device_name not in mqtt_data:
mqtt_data[device_name] = {
"time": datetime.utcnow().isoformat(),
"device": device_name,
"sensors": {}
}
# Update the sensor value in the mqtt_data dictionary
mqtt_data[device_name]["sensors"][sensor_type] = sensor_value
mqtt_data[device_name]["time"] = datetime.utcnow().isoformat()
# Store the updated data structure in Redis
redis_client.set(device_name, json.dumps(mqtt_data[device_name]))
print(f"Updated data for {device_name}: {mqtt_data[device_name]}")
except ValueError as e:
print(f"Error processing message payload: {e}")
mqtt_data[device_name]["time"] = str(datetime.now())
redis_client.set(device_name, json.dumps(mqtt_data))
print(f"Updated data for {device_name}: {mqtt_data[device_name]}")
def on_connect(client, userdata, flags, rc):
"""Handle successful connection."""
print(f"Connected with result code {rc}")
devices = get_devices()
for device in devices:
client.subscribe(f"{device['name']}/sensor/+/state")
"""Handle successful MQTT connection."""
if rc == 0:
print("Connected to MQTT Broker")
devices = get_mqtt_devices()
for device in devices:
client.subscribe(device['topic']) # Subscribing to each device's topic
print(f"Subscribed to topic: {device['topic']}")
else:
print(f"Failed to connect, return code {rc}")
def on_disconnect(client, userdata, rc):
"""Handle disconnection."""
if rc != 0:
print(f"Unexpected disconnection. Result code: {rc}")
"""Handle disconnection from MQTT broker."""
print(f"Disconnected with result code: {rc}")
def start_mqtt_client():
""" Start the MQTT client """
devices = get_devices()
"""Start the MQTT client to begin listening to topics."""
client = mqtt.Client()
client.on_message = on_message
client.on_connect = on_connect
client.on_disconnect = on_disconnect
client.on_message = on_message
client.connect(MQTT_BROKER)
client.loop_start()
print("MQTT Client Started")
for device in devices:
client.subscribe(f"{device['name']}/sensor/+/state")
# Keep the script running
try:
while True:
time.sleep(10) # Sleep to prevent high CPU usage
except KeyboardInterrupt:
print("Script interrupted by user")
finally:
client.loop_stop() # Stop the loop when exiting
client.loop_stop()
if __name__ == "__main__":
start_mqtt_client()
start_mqtt_client()

View File

@@ -2,91 +2,154 @@ import json
import datetime
import requests
import psycopg2
import redis
from django.conf import settings
from huey import crontab
from huey.contrib.djhuey import periodic_task
from .models import Device
import redis
from .models import Device, Sensor, SensorType
# Initialize Redis client
redis_client = redis.StrictRedis(host='10.10.0.1', port=6379, db=0)
def devices_to_redis():
"""Fetch all devices 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_list = [
{
'id': device.id,
'name': device.name,
'protocol': device.protocol,
'ip': device.ip,
}
for device in devices
]
redis_client.set('devices', json.dumps(devices_list))
devices_list = []
for device in devices:
for sensor in device.sensors.all():
sensor_data = {
'device_name': device.name,
'sensor_name': sensor.type.name,
'topic': sensor.type.topic # Assuming the topic is stored in SensorType
}
devices_list.append(sensor_data)
redis_client.set('mqtt_devices', json.dumps(devices_list))
print("Devices with sensors stored in Redis.")
def fetch_data_http(device):
"""Fetch temperature and humidity data from an HTTP sensor."""
data = {
"time": datetime.datetime.now().isoformat(),
"device": device.name,
}
def fetch_data_http(device, sensor):
"""Fetch data from an HTTP sensor."""
sensor_type_name = sensor.type.name.lower()
try:
temperature_response = requests.get(f"http://{device.ip}/sensor/tempreature")
humidity_response = requests.get(f"http://{device.ip}/sensor/humidity")
data["temperature"] = temperature_response.json().get('value')
data["humidity"] = humidity_response.json().get('value')
# Make the request to the device's HTTP endpoint
response = requests.get(f"http://{device.ip}/sensor/{sensor_type_name}", timeout=5)
response.raise_for_status() # Raise an exception for any non-200 status codes
sensor_value = response.json().get('value') # Assuming the JSON response structure
if sensor_value is not None:
return {
"time": datetime.datetime.utcnow().isoformat(),
"device": device.name,
"sensor": sensor_type_name,
"sensor_value": sensor_value
}
else:
print(f"No value returned from {device.name} for {sensor_type_name}")
except requests.RequestException as e:
print(f"HTTP request failed: {e}")
return data
print(f"HTTP request failed for {device.name}: {e}")
return None
def fetch_data_mqtt(device_name):
"""Fetch data from Redis for a specific MQTT device."""
data = redis_client.get(device_name)
def fetch_data_mqtt(device, sensor):
"""Fetch data from Redis for a specific MQTT device and sensor."""
# Get the data for the specific device from Redis
data = redis_client.get(device.name) # Assumes device.name is the Redis key
if data:
data = json.loads(data.decode('utf-8')).get(device_name)
if data and datetime.datetime.fromisoformat(data["time"]) > datetime.datetime.now() - datetime.timedelta(
minutes=2):
return data
data = json.loads(data.decode('utf-8'))
# Normalize the sensor name to lowercase for lookup
sensor_name = sensor.type.name.lower()
sensor_value = data['sensors'].get(sensor_name)
if sensor_value is not None and is_recent_data(data['time']):
return {
"time": data['time'],
"device": device.name,
"sensor_value": sensor_value
}
print(data)
return None
def insert_data(data):
"""Insert data into the PostgreSQL database."""
with psycopg2.connect(settings.CONNECTION_STRING) as conn:
with conn.cursor() as cursor:
insert_query = """
INSERT INTO conditions (time, device, temperature, humidity)
VALUES (%s, %s, %s, %s)
"""
cursor.execute(insert_query, (data["time"], data["device"], data["temperature"], data["humidity"]))
conn.commit()
def is_recent_data(timestamp):
"""Check if data is within a 2-minute freshness window."""
data_time = datetime.datetime.fromisoformat(timestamp)
return data_time > datetime.datetime.utcnow() - datetime.timedelta(minutes=2)
def insert_data(data, sensor_type):
"""Insert parsed data into the PostgreSQL database."""
if 'sensor_value' not in data:
print(f"Missing 'sensor_value' in data: {data}. Skipping insertion.")
return
insert_data_dict = {
"time": data['time'],
"device": data['device'],
"metric": sensor_type.lower(),
"value": data['sensor_value'],
}
try:
with psycopg2.connect(settings.CONNECTION_STRING) as conn:
with conn.cursor() as cursor:
insert_query = """
INSERT INTO sensor_readings (time, device_name, metric, value)
VALUES (%s, %s, %s, %s);
"""
cursor.execute(insert_query, (
insert_data_dict["time"],
insert_data_dict["device"],
insert_data_dict["metric"],
insert_data_dict["value"]
))
conn.commit()
print(f"Data inserted successfully for {insert_data_dict['device']}: {insert_data_dict}")
except Exception as e:
print(f"Failed to insert data: {e}")
@periodic_task(crontab(minute='*/1'))
def fetch_data_from_all_devices():
"""Fetch and insert data for all devices based on their protocol."""
devices = Device.objects.all()
for device in devices:
data = None
if device.protocol == 'http':
data = fetch_data_http(device)
elif device.protocol == 'mqtt':
data = fetch_data_mqtt(device.name)
for sensor in device.sensors.all():
data = None
if data:
data_time = datetime.datetime.fromisoformat(data["time"])
if data_time > datetime.datetime.now() - datetime.timedelta(minutes=1):
insert_data(data)
if device.protocol == 'http':
data = fetch_data_http(device, sensor)
elif device.protocol == 'mqtt':
data = fetch_data_mqtt(device, sensor)
if data and is_recent_data(data['time']):
insert_data(data, sensor.type.name)
else:
print(f"No recent data available for {device.name}. Skipping insertion.")
else:
print(f"No data available for {device.name}. Skipping insertion.")
print(f"No recent or valid data for {device.name}. Skipping.")
@periodic_task(crontab(minute='*/5'))
def last_5_minutes():
"""Fetch the last 5 readings from TimescaleDB and store them in Redis."""
try:
with psycopg2.connect(settings.CONNECTION_STRING) as conn:
with conn.cursor() as cursor:
cursor.execute("""
SELECT time, device_name, metric, value
FROM sensor_readings
ORDER BY time DESC
LIMIT 5;
""")
results = cursor.fetchall()
data = [
{
"time": reading[0].isoformat(),
"device": reading[1],
"metric": reading[2],
"value": reading[3]
}
for reading in results
]
redis_client.set("last5", json.dumps(data))
print("Last 5 readings:", data)
except Exception as e:
print(f"Error fetching or storing the last 5 readings: {e}")
# Initialize device data in Redis
devices_to_redis()

View File

@@ -1,222 +1,144 @@
<!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">
<title>IoT Sensor Dashboard</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/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;
}
.current-conditions {
text-align: center;
padding: 20px;
.device-panel {
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>
</head>
<body class="container my-4">
<!-- GPT Summary -->
<h4 class="text-center mb-4">{{ gpt }}</h4>
<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>
<h1 class="text-center mb-4">IoT Sensor Data Dashboard</h1>
<div class="container mt-5">
<h1 class="text-center mb-4">Temperature and Humidity Over Time</h1>
<!-- Button to add a new "island" (device panel) -->
<div class="text-center mb-4">
<button class="btn btn-success" onclick="addDevicePanel()">Add Device Panel</button>
</div>
<!-- Current Conditions -->
<div class="current-conditions">
<h2>Current Conditions</h2>
<div class="value" id="current-temperature">Loading...</div>
<div class="value" id="current-humidity">Loading...</div>
</div>
<!-- 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>
<!-- Container for dynamic device panels ("islands") -->
<div id="device-panels" class="row">
<!-- Dynamic panels will be added here -->
</div>
<script>
var ctx = document.getElementById('conditionsChart').getContext('2d');
var conditionsChart;
const devices = {{ devices_json|safe }};
let panelCount = 0;
const charts = {};
function fetchDeviceData() {
var device = document.getElementById('deviceSelector').value;
var startDate = document.getElementById('startDate').value;
var endDate = document.getElementById('endDate').value;
// Function to add a new device panel
function addDevicePanel() {
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>
fetch(`/fetch_device_data/?device=${device}&start_date=${startDate}&end_date=${endDate}`)
<!-- 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 => {
const option = document.createElement('option');
option.value = device.name;
option.text = `${device.name} (${device.sensors__type__name})`;
deviceSelect.appendChild(option);
});
}
// Function to remove a device panel
function removeDevicePanel(panelId) {
const panel = document.getElementById(panelId);
panel.remove();
}
// Function to fetch sensor data and update the chart for a specific panel
function fetchSensorData(panelId, chartId) {
const device = document.getElementById(`device-select-${panelId}`).value;
const startDate = document.getElementById(`start-date-${panelId}`).value;
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 => {
if (conditionsChart) {
conditionsChart.destroy(); // Destroy the old chart
}
const times = Object.values(data)[0].times;
const datasets = Object.keys(data).map(metric => ({
label: metric,
data: data[metric].values,
borderColor: getRandomColor(),
fill: false
}));
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,
}
}
}
});
// Check if the last recorded data is within the last 10 minutes
const lastRecordedTime = new Date(data.times[data.times.length - 1]);
const now = new Date();
const tenMinutesAgo = new Date(now.getTime() - 10 * 60000);
if (lastRecordedTime > tenMinutesAgo) {
document.getElementById('current-temperature').textContent = `Temperature: ${data.temperatures[data.temperatures.length - 1]}°C`;
document.getElementById('current-humidity').textContent = `Humidity: ${data.humidities[data.humidities.length - 1]}%`;
} else {
document.getElementById('current-temperature').textContent = `Temperature: -`;
document.getElementById('current-humidity').textContent = `Humidity: -`;
}
charts[chartId].data.labels = times;
charts[chartId].data.datasets = datasets;
charts[chartId].update();
});
}
// Initial load for the default device and time range
fetchDeviceData();
// Function to generate random color for chart lines
function getRandomColor() {
const letters = '0123456789ABCDEF';
let color = '#';
for (let i = 0; i < 6; i++) color += letters[Math.floor(Math.random() * 16)];
return color;
}
// Initialize with one device panel
addDevicePanel();
</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

@@ -5,9 +5,11 @@
<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">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</head>
<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="#">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">
@@ -16,7 +18,7 @@
<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>
<a class="nav-link" href="{% url 'index' %}">Chart</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'device_list' %}">Devices</a>
@@ -36,14 +38,29 @@
</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">
<!-- Display the form fields -->
{{ form.as_p }}
<!-- If there are errors, display them -->
{% if form.errors %}
<div class="alert alert-danger">
<ul>
{% for field, errors in form.errors.items %}
<li>{{ field }}: {{ errors|join:", " }}</li>
{% endfor %}
</ul>
</div>
{% endif %}
</div>
<!-- Submit and Cancel buttons -->
<button type="submit" class="btn btn-success">Save</button>
<a href="{% url 'device_list' %}" class="btn btn-secondary">Cancel</a>
</form>

View File

@@ -7,7 +7,8 @@
<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">
<!-- 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">
@@ -15,30 +16,23 @@
</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>
<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>
<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>
<li class="nav-item"><a class="nav-link" href="{% url 'login' %}">Login</a></li>
{% endif %}
</ul>
</div>
</div>
</nav>
<!-- Device Management -->
<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">
@@ -47,8 +41,7 @@
<th>Name</th>
<th>IP Address</th>
<th>Protocol</th>
<th>Temperature Monitoring</th>
<th>Humidity Monitoring</th>
<th>Sensor Types</th> <!-- Adjusted to Sensor Types -->
<th>Actions</th>
</tr>
</thead>
@@ -58,16 +51,27 @@
<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>
<!-- Collect and display sensor types -->
<td>
{% for sensor in device.sensors.all %}
{{ sensor.type.name }}{% if not forloop.last %}, {% endif %}
{% empty %}
No sensors
{% endfor %}
</td>
<!-- You may further expand other sensor-related data -->
<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>
{% empty %}
<tr>
<td colspan="5" class="text-center">No devices found.</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</body>
</html>
</html>

View File

@@ -20,12 +20,13 @@ from iotDashboard import views
urlpatterns = [
path('admin/', admin.site.urls),
path('',views.index),
path('devices_api/',views.devices_api),
path('',views.chart,name="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('logout/', views.logout_view, name='logout'),
path('sensor/add/',views.add_sensor_with_type,name="add_sensor_with_type")
]

View File

@@ -1,57 +1,105 @@
from django.http import HttpResponse, request, JsonResponse
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 .models import Device
from .forms import DeviceForm
from django.views.decorators.http import require_GET
from .models import Device, Sensor, SensorType
from .forms import DeviceForm, SensorWithTypeForm
import redis
redis_client = redis.StrictRedis(host='10.10.0.1', port=6379, db=0)
def fetch_gpt_data():
return redis_client.get("gpt").decode("utf-8").strip('b"').replace('\\"', '"').replace("\\n", "").replace("\\", "")
def chart(request):
# Fetch devices and their related sensors
devices = list(Device.objects.all().values('name', 'sensors__type__name'))
# Serialize data to JSON format
devices_json = json.dumps(devices, cls=DjangoJSONEncoder)
# Pass devices data to the context
gpt = fetch_gpt_data()
gpt = json.loads(gpt)
context = {'devices_json': devices_json, 'gpt': gpt["summary"]}
return render(request, 'chart.html', context)
# Fetch sensor data (AJAX)
def fetch_device_data(request):
device = request.GET.get('device', 'livingroom')
device_name = 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]
# 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"
query += " AND time >= %s::timestamptz"
params.append(start_date)
if end_date:
query += " AND time <= %s"
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()
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]
# 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')
return JsonResponse({
'times': times,
'temperatures': temperatures,
'humidities': humidities,
})
def chart(request):
devices = Device.objects.all()
context = {'devices': devices}
return render(request, 'chart.html', context)
if metric not in results:
results[metric] = {
'times': [],
'values': []
}
results[metric]['times'].append(formatted_time)
results[metric]['values'].append(value)
return JsonResponse(results)
def index(request):
if request.user.is_authenticated:
return redirect("/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)
@@ -62,6 +110,7 @@ def add_device(request):
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':
@@ -73,6 +122,7 @@ def edit_device(request, pk):
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':
@@ -80,5 +130,22 @@ def delete_device(request, pk):
return redirect('device_list')
return render(request, 'device_confirm_delete.html', {'device': device})
def logout_view():
redirect("/admin")
def add_sensor_with_type(request):
if request.method == 'POST':
form = SensorWithTypeForm(request.POST)
if form.is_valid():
form.save() # This will save both Sensor and SensorType as needed
return redirect('device_list') # Adjust this to your specific URL name
else:
form = SensorWithTypeForm()
context = {'form': form}
return render(request, 'sensor_form.html', context)
def logout_view(request):
return redirect("/admin")
def devices_api(request):
devices = list(Device.objects.all().values('name', 'sensors__type__name'))
return JsonResponse(devices, safe=False)