Refactor big.

This commit is contained in:
ferdzo
2024-10-10 00:36:03 +02:00
parent 5d37ec2396
commit 957abba1ee
15 changed files with 585 additions and 375 deletions

1
.gitignore vendored
View File

@@ -4,3 +4,4 @@ db.sqlite3
demo.db demo.db
demo.db-shm demo.db-shm
demo.db-wal demo.db-wal
__pycache__/

View File

@@ -1,7 +1,48 @@
from django import forms from django import forms
from .models import Device from .models import Device, Sensor, SensorType
# Form for adding/editing devices
class DeviceForm(forms.ModelForm): class DeviceForm(forms.ModelForm):
class Meta: class Meta:
model = Device model = Device
fields = ['name', 'ip', 'protocol', 'temperature', 'humidity'] fields = ['name', 'ip', 'protocol'] # Fields based on your Device model
widgets = {
'name': forms.TextInput(attrs={'class': 'form-control'}),
'ip': forms.TextInput(attrs={'class': 'form-control'}),
'protocol': forms.Select(attrs={'class': 'form-control'}),
}
# Form for adding a sensor with its type, including topic and endpoint for SensorType
class SensorWithTypeForm(forms.ModelForm):
type = forms.ModelChoiceField(
queryset=SensorType.objects.all(),
widget=forms.Select(attrs={'class': 'form-control'}),
label="Sensor Type"
)
class Meta:
model = Sensor
fields = ['device', 'type', 'enabled'] # Fields from your Sensor model
widgets = {
'device': forms.Select(attrs={'class': 'form-control'}),
'enabled': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
}
def save(self, commit=True):
sensor = super().save(commit=False)
if commit:
sensor.save()
return sensor
# Form for creating or editing SensorType
class SensorTypeForm(forms.ModelForm):
class Meta:
model = SensorType
fields = ['name', 'unit', 'protocol', 'topic', 'endpoint'] # Fields from your SensorType model
widgets = {
'name': forms.TextInput(attrs={'class': 'form-control'}),
'unit': forms.TextInput(attrs={'class': 'form-control'}),
'protocol': forms.Select(attrs={'class': 'form-control'}),
'topic': forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'Optional for MQTT'}),
'endpoint': forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'Optional for HTTP'}),
}

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,25 +0,0 @@
# Generated by Django 4.2.5 on 2024-08-28 19:45
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Device',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('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)),
],
),
]

View File

@@ -1,10 +1,26 @@
from django.db import models from django.db import models
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 f"{self.name} ({self.unit})"
class Device(models.Model): class Device(models.Model):
name = models.CharField(max_length=50) name = models.CharField(max_length=50) # Device name
ip = models.CharField(max_length=20) ip = models.CharField(max_length=20) # Device IP address
protocol = models.CharField(max_length=20) protocol = models.CharField(max_length=20, choices=[('mqtt', 'MQTT'), ('http', 'HTTP')])
temperature = models.BooleanField(default=False)
humidity = models.BooleanField(default=False)
def __str__(self): def __str__(self):
return self.name 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,82 +6,103 @@ import paho.mqtt.client as mqtt
import redis import redis
from dotenv import load_dotenv from dotenv import load_dotenv
# Load environment variables
load_dotenv() load_dotenv()
# Set up Redis client
REDIS_HOST = os.getenv('REDIS_HOST', 'localhost') REDIS_HOST = os.getenv('REDIS_HOST', 'localhost')
redis_client = redis.StrictRedis(host=REDIS_HOST, port=6379, db=0) redis_client = redis.StrictRedis(host=REDIS_HOST, port=6379, db=0)
print("Connected to Redis Server") print("Connected to Redis Server")
# MQTT broker address
MQTT_BROKER = os.getenv('MQTT_BROKER') MQTT_BROKER = os.getenv('MQTT_BROKER')
mqtt_data = {} mqtt_data = {}
def get_devices(): def get_mqtt_devices():
"""" Simple method to get all devices """ """Retrieve MQTT devices and sensor details from Redis."""
# Get devices from Redis devices_json = redis_client.get('mqtt_devices')
devices_json = redis_client.get('devices')
if devices_json: if devices_json:
return json.loads(devices_json) return json.loads(devices_json)
return [] 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): def on_message(client, userdata, msg):
"""" Simple method to handle messages """ """Handle incoming messages from MQTT broker."""
topic = msg.topic.split('/') try:
device_name = topic[0] # Parse the incoming message topic
sensor = topic[-2] 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
sensor_value = float(msg.payload.decode())
print(f"Received message from {device_endpoint}, sensor {sensor_type}: {sensor_value}")
# 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: if device_name not in mqtt_data:
mqtt_data[device_name] = {"time": datetime.now(), mqtt_data[device_name] = {
"time": datetime.utcnow().isoformat(),
"device": device_name, "device": device_name,
"temperature": None, "sensors": {}
"humidity": None} }
if sensor == "tempreature": # Update the sensor value in the mqtt_data dictionary
mqtt_data[device_name]["temperature"] = float(msg.payload.decode()) mqtt_data[device_name]["sensors"][sensor_type] = sensor_value
elif sensor == "humidity": mqtt_data[device_name]["time"] = datetime.utcnow().isoformat()
mqtt_data[device_name]["humidity"] = float(msg.payload.decode())
mqtt_data[device_name]["time"] = str(datetime.now()) # Store the updated data structure in Redis
redis_client.set(device_name, json.dumps(mqtt_data)) redis_client.set(device_name, json.dumps(mqtt_data[device_name]))
print(f"Updated data for {device_name}: {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}")
def on_connect(client, userdata, flags, rc): def on_connect(client, userdata, flags, rc):
"""Handle successful connection.""" """Handle successful MQTT connection."""
print(f"Connected with result code {rc}") if rc == 0:
devices = get_devices() print("Connected to MQTT Broker")
devices = get_mqtt_devices()
for device in devices: for device in devices:
client.subscribe(f"{device['name']}/sensor/+/state") 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): def on_disconnect(client, userdata, rc):
"""Handle disconnection.""" """Handle disconnection from MQTT broker."""
if rc != 0: print(f"Disconnected with result code: {rc}")
print(f"Unexpected disconnection. Result code: {rc}")
def start_mqtt_client(): def start_mqtt_client():
""" Start the MQTT client """ """Start the MQTT client to begin listening to topics."""
devices = get_devices()
client = mqtt.Client() client = mqtt.Client()
client.on_message = on_message
client.on_connect = on_connect client.on_connect = on_connect
client.on_disconnect = on_disconnect client.on_disconnect = on_disconnect
client.on_message = on_message
client.connect(MQTT_BROKER) client.connect(MQTT_BROKER)
client.loop_start() client.loop_start()
print("MQTT Client Started") print("MQTT Client Started")
for device in devices:
client.subscribe(f"{device['name']}/sensor/+/state")
# Keep the script running
try: try:
while True: while True:
time.sleep(10) # Sleep to prevent high CPU usage time.sleep(10) # Sleep to prevent high CPU usage
except KeyboardInterrupt: except KeyboardInterrupt:
print("Script interrupted by user") print("Script interrupted by user")
finally: finally:
client.loop_stop() # Stop the loop when exiting client.loop_stop()
if __name__ == "__main__": if __name__ == "__main__":

View File

@@ -2,91 +2,154 @@ import json
import datetime import datetime
import requests import requests
import psycopg2 import psycopg2
import redis
from django.conf import settings from django.conf import settings
from huey import crontab from huey import crontab
from huey.contrib.djhuey import periodic_task from huey.contrib.djhuey import periodic_task
from .models import Device from .models import Device, Sensor, SensorType
import redis
# 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 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 = Device.objects.all()
devices_list = [ devices_list = []
{ for device in devices:
'id': device.id, for sensor in device.sensors.all():
'name': device.name, sensor_data = {
'protocol': device.protocol, 'device_name': device.name,
'ip': device.ip, 'sensor_name': sensor.type.name,
'topic': sensor.type.topic # Assuming the topic is stored in SensorType
} }
for device in devices devices_list.append(sensor_data)
] redis_client.set('mqtt_devices', json.dumps(devices_list))
redis_client.set('devices', json.dumps(devices_list)) print("Devices with sensors stored in Redis.")
def fetch_data_http(device, sensor):
def fetch_data_http(device): """Fetch data from an HTTP sensor."""
"""Fetch temperature and humidity data from an HTTP sensor.""" sensor_type_name = sensor.type.name.lower()
data = {
"time": datetime.datetime.now().isoformat(),
"device": device.name,
}
try: try:
temperature_response = requests.get(f"http://{device.ip}/sensor/tempreature") # Make the request to the device's HTTP endpoint
humidity_response = requests.get(f"http://{device.ip}/sensor/humidity") response = requests.get(f"http://{device.ip}/sensor/{sensor_type_name}", timeout=5)
data["temperature"] = temperature_response.json().get('value') response.raise_for_status() # Raise an exception for any non-200 status codes
data["humidity"] = humidity_response.json().get('value') 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: except requests.RequestException as e:
print(f"HTTP request failed: {e}") print(f"HTTP request failed for {device.name}: {e}")
return data return None
def fetch_data_mqtt(device, sensor):
def fetch_data_mqtt(device_name): """Fetch data from Redis for a specific MQTT device and sensor."""
"""Fetch data from Redis for a specific MQTT device.""" # Get the data for the specific device from Redis
data = redis_client.get(device_name) data = redis_client.get(device.name) # Assumes device.name is the Redis key
if data: if data:
data = json.loads(data.decode('utf-8')).get(device_name) data = json.loads(data.decode('utf-8'))
if data and datetime.datetime.fromisoformat(data["time"]) > datetime.datetime.now() - datetime.timedelta(
minutes=2): # Normalize the sensor name to lowercase for lookup
return data 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 return None
def insert_data(data): def is_recent_data(timestamp):
"""Insert data into the PostgreSQL database.""" """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 psycopg2.connect(settings.CONNECTION_STRING) as conn:
with conn.cursor() as cursor: with conn.cursor() as cursor:
insert_query = """ insert_query = """
INSERT INTO conditions (time, device, temperature, humidity) INSERT INTO sensor_readings (time, device_name, metric, value)
VALUES (%s, %s, %s, %s) VALUES (%s, %s, %s, %s);
""" """
cursor.execute(insert_query, (data["time"], data["device"], data["temperature"], data["humidity"])) cursor.execute(insert_query, (
insert_data_dict["time"],
insert_data_dict["device"],
insert_data_dict["metric"],
insert_data_dict["value"]
))
conn.commit() 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')) @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."""
devices = Device.objects.all() devices = Device.objects.all()
for device in devices: for device in devices:
for sensor in device.sensors.all():
data = None data = None
if device.protocol == 'http': if device.protocol == 'http':
data = fetch_data_http(device) data = fetch_data_http(device, sensor)
elif device.protocol == 'mqtt': elif device.protocol == 'mqtt':
data = fetch_data_mqtt(device.name) data = fetch_data_mqtt(device, sensor)
if data: if data and is_recent_data(data['time']):
data_time = datetime.datetime.fromisoformat(data["time"]) insert_data(data, sensor.type.name)
if data_time > datetime.datetime.now() - datetime.timedelta(minutes=1):
insert_data(data)
else: else:
print(f"No recent data available for {device.name}. Skipping insertion.") print(f"No recent or valid data for {device.name}. Skipping.")
else:
print(f"No data available for {device.name}. Skipping insertion.")
@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 # Initialize device data in Redis
devices_to_redis() devices_to_redis()

View File

@@ -1,222 +1,144 @@
<!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>Conditions Chart with Chart.js</title> <title>IoT Sensor Dashboard</title>
<!-- Bootstrap CSS --> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/css/bootstrap.min.css" rel="stylesheet">
<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> <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style> <style>
.chart-container { .device-panel {
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;
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 class="container my-4">
<!-- GPT Summary -->
<h4 class="text-center mb-4">{{ gpt }}</h4>
<body class="bg-light"> <h1 class="text-center mb-4">IoT Sensor Data Dashboard</h1>
<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"> <!-- Button to add a new "island" (device panel) -->
<h1 class="text-center mb-4">Temperature and Humidity Over Time</h1> <div class="text-center mb-4">
<button class="btn btn-success" onclick="addDevicePanel()">Add Device Panel</button>
<!-- 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> </div>
<!-- Device Selector Dropdown --> <!-- Container for dynamic device panels ("islands") -->
<div class="row mb-4"> <div id="device-panels" class="row">
<div class="col-md-4 offset-md-4"> <!-- Dynamic panels will be added here -->
<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> </div>
<script> <script>
var ctx = document.getElementById('conditionsChart').getContext('2d'); const devices = {{ devices_json|safe }};
var conditionsChart; let panelCount = 0;
const charts = {};
function fetchDeviceData() { // Function to add a new device panel
var device = document.getElementById('deviceSelector').value; function addDevicePanel() {
var startDate = document.getElementById('startDate').value; const panelId = `device-panel-${panelCount}`;
var endDate = document.getElementById('endDate').value; 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(response => response.json())
.then(data => { .then(data => {
if (conditionsChart) { const times = Object.values(data)[0].times;
conditionsChart.destroy(); // Destroy the old chart const datasets = Object.keys(data).map(metric => ({
} label: metric,
data: data[metric].values,
borderColor: getRandomColor(),
fill: false
}));
conditionsChart = new Chart(ctx, { charts[chartId].data.labels = times;
type: 'line', charts[chartId].data.datasets = datasets;
data: { charts[chartId].update();
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: -`;
}
}); });
} }
// Initial load for the default device and time range // Function to generate random color for chart lines
fetchDeviceData(); 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> </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> </body>
</html> </html>

View File

@@ -16,7 +16,7 @@
<div class="collapse navbar-collapse" id="navbarNav"> <div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto mb-2 mb-lg-0"> <ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="{% url 'chart' %}">Chart</a> <a class="nav-link" href="{% url 'index' %}">Chart</a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="{% url 'device_list' %}">Devices</a> <a class="nav-link" href="{% url 'device_list' %}">Devices</a>

View File

@@ -5,7 +5,9 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% if form.instance.pk %}Edit{% else %}Add{% endif %} Device</title> <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"> <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> </head>
<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"> <div class="container-fluid">
@@ -16,7 +18,7 @@
<div class="collapse navbar-collapse" id="navbarNav"> <div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto mb-2 mb-lg-0"> <ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="{% url 'chart' %}">Chart</a> <a class="nav-link" href="{% url 'index' %}">Chart</a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="{% url 'device_list' %}">Devices</a> <a class="nav-link" href="{% url 'device_list' %}">Devices</a>
@@ -36,14 +38,29 @@
</div> </div>
</div> </div>
</nav> </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>
<form method="post"> <form method="post">
{% csrf_token %} {% csrf_token %}
<div class="mb-3"> <div class="mb-3">
<!-- Display the form fields -->
{{ form.as_p }} {{ 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> </div>
{% endif %}
</div>
<!-- Submit and Cancel buttons -->
<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

@@ -7,6 +7,7 @@
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head> </head>
<body class="bg-light"> <body class="bg-light">
<!-- Navbar -->
<nav class="navbar navbar-expand-lg navbar-light bg-light"> <nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="container-fluid"> <div class="container-fluid">
<a class="navbar-brand" href="#">IoT Dashboard</a> <a class="navbar-brand" href="#">IoT Dashboard</a>
@@ -15,30 +16,23 @@
</button> </button>
<div class="collapse navbar-collapse" id="navbarNav"> <div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto mb-2 mb-lg-0"> <ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item"> <li class="nav-item"><a class="nav-link" href="{% url 'index' %}">Chart</a></li>
<a class="nav-link" href="{% url 'chart' %}">Chart</a> <li class="nav-item"><a class="nav-link" href="{% url 'device_list' %}">Devices</a></li>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'device_list' %}">Devices</a>
</li>
</ul> </ul>
<ul class="navbar-nav"> <ul class="navbar-nav">
{% if user.is_authenticated %} {% if user.is_authenticated %}
<li class="nav-item"> <li class="nav-item"><a class="nav-link" href="{% url 'logout' %}">Logout</a></li>
<a class="nav-link" href="{% url 'logout' %}">Logout</a>
</li>
{% else %} {% else %}
<li class="nav-item"> <li class="nav-item"><a class="nav-link" href="{% url 'login' %}">Login</a></li>
<a class="nav-link" href="{% url 'login' %}">Login</a>
</li>
{% endif %} {% endif %}
</ul> </ul>
</div> </div>
</div> </div>
</nav> </nav>
<!-- Device Management -->
<div class="container mt-5"> <div class="container mt-5">
<h1 class="text-center mb-4">Manage Devices</h1> <h1 class="text-center mb-4">Manage Devices</h1>
<a href="{% url 'add_device' %}" class="btn btn-primary mb-3">Add Device</a> <a href="{% url 'add_device' %}" class="btn btn-primary mb-3">Add Device</a>
<table class="table table-striped"> <table class="table table-striped">
@@ -47,8 +41,7 @@
<th>Name</th> <th>Name</th>
<th>IP Address</th> <th>IP Address</th>
<th>Protocol</th> <th>Protocol</th>
<th>Temperature Monitoring</th> <th>Sensor Types</th> <!-- Adjusted to Sensor Types -->
<th>Humidity Monitoring</th>
<th>Actions</th> <th>Actions</th>
</tr> </tr>
</thead> </thead>
@@ -58,13 +51,24 @@
<td>{{ device.name }}</td> <td>{{ device.name }}</td>
<td>{{ device.ip }}</td> <td>{{ device.ip }}</td>
<td>{{ device.protocol }}</td> <td>{{ device.protocol }}</td>
<td>{{ device.temperature|yesno:"Yes,No" }}</td> <!-- Collect and display sensor types -->
<td>{{ device.humidity|yesno:"Yes,No" }}</td> <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> <td>
<a href="{% url 'edit_device' device.pk %}" class="btn btn-warning btn-sm">Edit</a> <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> <a href="{% url 'delete_device' device.pk %}" class="btn btn-danger btn-sm">Delete</a>
</td> </td>
</tr> </tr>
{% empty %}
<tr>
<td colspan="5" class="text-center">No devices found.</td>
</tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>

View File

@@ -0,0 +1,41 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Add Sensor and Type</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>
<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 'device_list' %}">Devices</a></li>
</ul>
</div>
</div>
</nav>
<div class="container mt-5">
<h1 class="text-center mb-4">Add Sensor and Sensor Type</h1>
<form method="post">
{% csrf_token %}
{{ form.as_p }} <!-- Renders the inputs for all fields you added in your form -->
<button type="submit" class="btn btn-success">Save</button>
<a href="{% url 'device_list' %}" class="btn btn-secondary">Cancel</a>
</form>
{% 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>
</body>
</html>

View File

@@ -20,12 +20,13 @@ from iotDashboard import views
urlpatterns = [ urlpatterns = [
path('admin/', admin.site.urls), 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('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/', views.device_list, name='device_list'),
path('devices/add/', views.add_device, name='add_device'), path('devices/add/', views.add_device, name='add_device'),
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")
] ]

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.db import connections
from django.shortcuts import render, redirect, get_object_or_404 from django.shortcuts import render, redirect, get_object_or_404
from .models import Device from django.views.decorators.http import require_GET
from .forms import DeviceForm
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): 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') start_date = request.GET.get('start_date')
end_date = request.GET.get('end_date') end_date = request.GET.get('end_date')
query = """ # Log the parameters to ensure they are correct
SELECT time, temperature, humidity print("Device Name:", device_name)
FROM conditions print("Start Date:", start_date)
WHERE device = %s print("End Date:", end_date)
"""
params = [device]
# 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: if start_date:
query += " AND time >= %s" query += " AND time >= %s::timestamptz"
params.append(start_date) params.append(start_date)
if end_date: if end_date:
query += " AND time <= %s" query += " AND time <= %s::timestamptz"
params.append(end_date) 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: with connections["data"].cursor() as cursor:
cursor.execute(query, params) cursor.execute(query, params)
rows = cursor.fetchall() rows = cursor.fetchall()
times = [row[0].strftime('%Y-%m-%d %H:%M:%S') for row in rows] # Process the results and group them by sensor type (metric)
temperatures = [row[1] for row in rows] for row in rows:
humidities = [row[2] for row in rows] time, metric, value = row
formatted_time = time.strftime('%Y-%m-%d %H:%M:%S')
return JsonResponse({ if metric not in results:
'times': times, results[metric] = {
'temperatures': temperatures, 'times': [],
'humidities': humidities, 'values': []
}) }
def chart(request): results[metric]['times'].append(formatted_time)
devices = Device.objects.all() results[metric]['values'].append(value)
context = {'devices': devices}
return render(request, 'chart.html', context) 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/")
return HttpResponse("NOT AUTHENTICATED!!!") return HttpResponse("NOT AUTHENTICATED!!!")
def device_list(request): def device_list(request):
devices = Device.objects.all() devices = Device.objects.all()
return render(request, 'device_list.html', {'devices': devices}) return render(request, 'device_list.html', {'devices': devices})
def add_device(request): def add_device(request):
if request.method == 'POST': if request.method == 'POST':
form = DeviceForm(request.POST) form = DeviceForm(request.POST)
@@ -62,6 +110,7 @@ def add_device(request):
form = DeviceForm() form = DeviceForm()
return render(request, 'device_form.html', {'form': form}) return render(request, 'device_form.html', {'form': form})
def edit_device(request, pk): def edit_device(request, pk):
device = get_object_or_404(Device, pk=pk) device = get_object_or_404(Device, pk=pk)
if request.method == 'POST': if request.method == 'POST':
@@ -73,6 +122,7 @@ def edit_device(request, pk):
form = DeviceForm(instance=device) form = DeviceForm(instance=device)
return render(request, 'device_form.html', {'form': form}) return render(request, 'device_form.html', {'form': form})
def delete_device(request, pk): def delete_device(request, pk):
device = get_object_or_404(Device, pk=pk) device = get_object_or_404(Device, pk=pk)
if request.method == 'POST': if request.method == 'POST':
@@ -80,5 +130,22 @@ 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 logout_view(): def add_sensor_with_type(request):
redirect("/admin") 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)