mirror of
https://github.com/ferdzo/iotDashboard.git
synced 2026-04-05 01:06:24 +00:00
BIN
__pycache__/proba.cpython-311.pyc
Normal file
BIN
__pycache__/proba.cpython-311.pyc
Normal file
Binary file not shown.
BIN
iotDashboard/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
iotDashboard/__pycache__/__init__.cpython-311.pyc
Normal file
Binary file not shown.
BIN
iotDashboard/__pycache__/apps.cpython-311.pyc
Normal file
BIN
iotDashboard/__pycache__/apps.cpython-311.pyc
Normal file
Binary file not shown.
BIN
iotDashboard/__pycache__/forms.cpython-311.pyc
Normal file
BIN
iotDashboard/__pycache__/forms.cpython-311.pyc
Normal file
Binary file not shown.
BIN
iotDashboard/__pycache__/getInfoFromDevices.cpython-311.pyc
Normal file
BIN
iotDashboard/__pycache__/getInfoFromDevices.cpython-311.pyc
Normal file
Binary file not shown.
BIN
iotDashboard/__pycache__/models.cpython-311.pyc
Normal file
BIN
iotDashboard/__pycache__/models.cpython-311.pyc
Normal file
Binary file not shown.
BIN
iotDashboard/__pycache__/settings.cpython-311.pyc
Normal file
BIN
iotDashboard/__pycache__/settings.cpython-311.pyc
Normal file
Binary file not shown.
BIN
iotDashboard/__pycache__/tasks.cpython-311.pyc
Normal file
BIN
iotDashboard/__pycache__/tasks.cpython-311.pyc
Normal file
Binary file not shown.
BIN
iotDashboard/__pycache__/urls.cpython-311.pyc
Normal file
BIN
iotDashboard/__pycache__/urls.cpython-311.pyc
Normal file
Binary file not shown.
BIN
iotDashboard/__pycache__/views.cpython-311.pyc
Normal file
BIN
iotDashboard/__pycache__/views.cpython-311.pyc
Normal file
Binary file not shown.
BIN
iotDashboard/__pycache__/wsgi.cpython-311.pyc
Normal file
BIN
iotDashboard/__pycache__/wsgi.cpython-311.pyc
Normal file
Binary file not shown.
54
iotDashboard/db_create.py
Normal file
54
iotDashboard/db_create.py
Normal 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()
|
||||||
@@ -1,7 +1,72 @@
|
|||||||
from django import forms
|
from django import forms
|
||||||
from .models import Device
|
from .models import Device, Sensor, SensorType
|
||||||
|
|
||||||
class DeviceForm(forms.ModelForm):
|
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:
|
class Meta:
|
||||||
model = Device
|
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
|
||||||
41
iotDashboard/gpt.py
Normal file
41
iotDashboard/gpt.py
Normal 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)
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
# Generated by Django 4.2.5 on 2024-10-08 10:51
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('iotDashboard', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='SensorType',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(max_length=50, unique=True)),
|
||||||
|
('unit', models.CharField(max_length=20)),
|
||||||
|
('protocol', models.CharField(choices=[('mqtt', 'MQTT'), ('http', 'HTTP')], max_length=20)),
|
||||||
|
('topic', models.CharField(blank=True, max_length=100, null=True)),
|
||||||
|
('endpoint', models.CharField(blank=True, max_length=100, null=True)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='device',
|
||||||
|
name='humidity',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='device',
|
||||||
|
name='temperature',
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='device',
|
||||||
|
name='protocol',
|
||||||
|
field=models.CharField(choices=[('mqtt', 'MQTT'), ('http', 'HTTP')], max_length=20),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Sensor',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('enabled', models.BooleanField(default=True)),
|
||||||
|
('device', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sensors', to='iotDashboard.device')),
|
||||||
|
('type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='iotDashboard.sensortype')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
BIN
iotDashboard/migrations/__pycache__/0001_initial.cpython-311.pyc
Normal file
BIN
iotDashboard/migrations/__pycache__/0001_initial.cpython-311.pyc
Normal file
Binary file not shown.
BIN
iotDashboard/migrations/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
iotDashboard/migrations/__pycache__/__init__.cpython-311.pyc
Normal file
Binary file not shown.
@@ -1,10 +1,26 @@
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
class Device(models.Model):
|
|
||||||
name = models.CharField(max_length=50)
|
class SensorType(models.Model):
|
||||||
ip = models.CharField(max_length=20)
|
name = models.CharField(max_length=50, unique=True) # Sensor name, e.g., "CO2", "Noise", etc.
|
||||||
protocol = models.CharField(max_length=20)
|
unit = models.CharField(max_length=20) # Unit of measurement, e.g., "ppm", "dB", "lux"
|
||||||
temperature = models.BooleanField(default=False)
|
protocol = models.CharField(max_length=20, choices=[('mqtt', 'MQTT'), ('http', 'HTTP')]) # Protocol for communication
|
||||||
humidity = models.BooleanField(default=False)
|
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):
|
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}"
|
||||||
|
|||||||
@@ -6,83 +6,104 @@ 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
|
||||||
|
|
||||||
if device_name not in mqtt_data:
|
sensor_value = float(msg.payload.decode())
|
||||||
mqtt_data[device_name] = {"time": datetime.now(),
|
print(f"Received message from {device_endpoint}, sensor {sensor_type}: {sensor_value}")
|
||||||
"device": device_name,
|
|
||||||
"temperature": None,
|
|
||||||
"humidity": None}
|
|
||||||
|
|
||||||
if sensor == "tempreature":
|
# Build the device map to get the friendly device name
|
||||||
mqtt_data[device_name]["temperature"] = float(msg.payload.decode())
|
device_map = build_device_map()
|
||||||
elif sensor == "humidity":
|
device_name = device_map.get(device_endpoint, device_endpoint) # Fallback to endpoint if not found
|
||||||
mqtt_data[device_name]["humidity"] = float(msg.payload.decode())
|
|
||||||
|
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):
|
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")
|
||||||
for device in devices:
|
devices = get_mqtt_devices()
|
||||||
client.subscribe(f"{device['name']}/sensor/+/state")
|
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):
|
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__":
|
||||||
start_mqtt_client()
|
start_mqtt_client()
|
||||||
|
|||||||
@@ -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('devices', json.dumps(devices_list))
|
redis_client.set('mqtt_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."""
|
||||||
with psycopg2.connect(settings.CONNECTION_STRING) as conn:
|
data_time = datetime.datetime.fromisoformat(timestamp)
|
||||||
with conn.cursor() as cursor:
|
return data_time > datetime.datetime.utcnow() - datetime.timedelta(minutes=2)
|
||||||
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 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'))
|
@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:
|
||||||
data = None
|
for sensor in device.sensors.all():
|
||||||
if device.protocol == 'http':
|
data = None
|
||||||
data = fetch_data_http(device)
|
|
||||||
elif device.protocol == 'mqtt':
|
|
||||||
data = fetch_data_mqtt(device.name)
|
|
||||||
|
|
||||||
if data:
|
if device.protocol == 'http':
|
||||||
data_time = datetime.datetime.fromisoformat(data["time"])
|
data = fetch_data_http(device, sensor)
|
||||||
if data_time > datetime.datetime.now() - datetime.timedelta(minutes=1):
|
elif device.protocol == 'mqtt':
|
||||||
insert_data(data)
|
data = fetch_data_mqtt(device, sensor)
|
||||||
|
|
||||||
|
if data and is_recent_data(data['time']):
|
||||||
|
insert_data(data, sensor.type.name)
|
||||||
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()
|
||||||
|
|||||||
@@ -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>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Current Conditions -->
|
<!-- Container for dynamic device panels ("islands") -->
|
||||||
<div class="current-conditions">
|
<div id="device-panels" class="row">
|
||||||
<h2>Current Conditions</h2>
|
<!-- Dynamic panels will be added here -->
|
||||||
<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>
|
|
||||||
</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>
|
||||||
|
|||||||
@@ -5,9 +5,11 @@
|
|||||||
<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">
|
||||||
<a class="navbar-brand" href="#">IoT Dashboard</a>
|
<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">
|
<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">
|
<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>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</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>
|
||||||
|
|||||||
@@ -7,7 +7,8 @@
|
|||||||
<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">
|
||||||
<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">
|
<div class="container-fluid">
|
||||||
<a class="navbar-brand" href="#">IoT Dashboard</a>
|
<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">
|
<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>
|
</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,16 +51,27 @@
|
|||||||
<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>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
41
iotDashboard/templates/sensor_form.html
Normal file
41
iotDashboard/templates/sensor_form.html
Normal 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>
|
||||||
@@ -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")
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user