mirror of
https://github.com/ferdzo/iotDashboard.git
synced 2026-04-04 16:56:25 +00:00
Update chart.html, experimenting with MQTT.
This commit is contained in:
@@ -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 .models import Device
|
||||
from .models import Device, Sensor, SensorType
|
||||
|
||||
class DeviceForm(forms.ModelForm):
|
||||
# Optionally include sensors as choices in the form if relevant
|
||||
sensors = forms.ModelMultipleChoiceField(
|
||||
queryset=Sensor.objects.all(),
|
||||
required=False,
|
||||
widget=forms.CheckboxSelectMultiple,
|
||||
label='Sensors'
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Device
|
||||
fields = ['name', 'ip', 'protocol', 'temperature', 'humidity']
|
||||
fields = ['name', 'ip', 'protocol']
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
# Optionally pass initial sensors for editing an existing device
|
||||
if 'instance' in kwargs:
|
||||
initial_sensors = kwargs['instance'].sensors.all() if kwargs['instance'] else None
|
||||
initial = kwargs.get('initial', {})
|
||||
initial['sensors'] = initial_sensors
|
||||
kwargs['initial'] = initial
|
||||
super(DeviceForm, self).__init__(*args, **kwargs)
|
||||
|
||||
def save(self, commit=True):
|
||||
# Save the device instance
|
||||
device = super(DeviceForm, self).save(commit=False)
|
||||
|
||||
if commit:
|
||||
device.save()
|
||||
self.save_m2m() # Ensure M2M save happens
|
||||
|
||||
return device
|
||||
class SensorWithTypeForm(forms.ModelForm):
|
||||
# Add fields for SensorType directly in the form
|
||||
type_name = forms.CharField(max_length=50, label="Sensor Type Name")
|
||||
unit = forms.CharField(max_length=20, label="Unit", required=False)
|
||||
protocol = forms.ChoiceField(
|
||||
choices=[('mqtt', 'MQTT'), ('http', 'HTTP')],
|
||||
label="Protocol"
|
||||
)
|
||||
topic = forms.CharField(max_length=100, label="Topic", required=False)
|
||||
endpoint = forms.CharField(max_length=100, label="Endpoint", required=False)
|
||||
|
||||
class Meta:
|
||||
model = Sensor
|
||||
fields = ['device', 'enabled']
|
||||
|
||||
def save(self, commit=True):
|
||||
# Create or get the SensorType
|
||||
try:
|
||||
sensor_type = SensorType.objects.get(name=self.cleaned_data['type_name'])
|
||||
except SensorType.DoesNotExist:
|
||||
sensor_type = SensorType(
|
||||
name=self.cleaned_data['type_name'],
|
||||
unit=self.cleaned_data['unit'],
|
||||
protocol=self.cleaned_data['protocol'],
|
||||
topic=self.cleaned_data['topic'],
|
||||
endpoint=self.cleaned_data['endpoint']
|
||||
)
|
||||
if commit:
|
||||
sensor_type.save()
|
||||
|
||||
# Create Sensor with the SensorType found or created
|
||||
sensor = super(SensorWithTypeForm, self).save(commit=False)
|
||||
sensor.type = sensor_type
|
||||
|
||||
if commit:
|
||||
sensor.save()
|
||||
|
||||
return sensor
|
||||
@@ -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)
|
||||
|
||||
@@ -1,10 +1,26 @@
|
||||
from django.db import models
|
||||
class Device(models.Model):
|
||||
name = models.CharField(max_length=50)
|
||||
ip = models.CharField(max_length=20)
|
||||
protocol = models.CharField(max_length=20)
|
||||
temperature = models.BooleanField(default=False)
|
||||
humidity = models.BooleanField(default=False)
|
||||
|
||||
class SensorType(models.Model):
|
||||
name = models.CharField(max_length=50, unique=True) # Sensor name, e.g., "CO2", "Noise", etc.
|
||||
unit = models.CharField(max_length=20) # Unit of measurement, e.g., "ppm", "dB", "lux"
|
||||
protocol = models.CharField(max_length=20, choices=[('mqtt', 'MQTT'), ('http', 'HTTP')]) # Protocol for communication
|
||||
topic = models.CharField(max_length=100, null=True, blank=True) # Topic for MQTT communication
|
||||
endpoint = models.CharField(max_length=100, null=True, blank=True) # Endpoint for HTTP communication
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
return f"{self.name} ({self.unit})"
|
||||
|
||||
class Device(models.Model):
|
||||
name = models.CharField(max_length=50) # Device name
|
||||
ip = models.CharField(max_length=20) # Device IP address
|
||||
protocol = models.CharField(max_length=20, choices=[('mqtt', 'MQTT'), ('http', 'HTTP')])
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class Sensor(models.Model):
|
||||
device = models.ForeignKey(Device, related_name='sensors', on_delete=models.CASCADE)
|
||||
type = models.ForeignKey(SensorType, on_delete=models.CASCADE)
|
||||
enabled = models.BooleanField(default=True)
|
||||
def __str__(self):
|
||||
return f"{self.type.name} Sensor on {self.device.name}"
|
||||
|
||||
@@ -6,83 +6,104 @@ import paho.mqtt.client as mqtt
|
||||
import redis
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Load environment variables
|
||||
load_dotenv()
|
||||
|
||||
# Set up Redis client
|
||||
REDIS_HOST = os.getenv('REDIS_HOST', 'localhost')
|
||||
redis_client = redis.StrictRedis(host=REDIS_HOST, port=6379, db=0)
|
||||
|
||||
print("Connected to Redis Server")
|
||||
|
||||
# MQTT broker address
|
||||
MQTT_BROKER = os.getenv('MQTT_BROKER')
|
||||
|
||||
mqtt_data = {}
|
||||
|
||||
|
||||
def get_devices():
|
||||
"""" Simple method to get all devices """
|
||||
# Get devices from Redis
|
||||
devices_json = redis_client.get('devices')
|
||||
def get_mqtt_devices():
|
||||
"""Retrieve MQTT devices and sensor details from Redis."""
|
||||
devices_json = redis_client.get('mqtt_devices')
|
||||
if devices_json:
|
||||
return json.loads(devices_json)
|
||||
return []
|
||||
|
||||
|
||||
def build_device_map():
|
||||
"""Build a mapping of device endpoints to friendly names."""
|
||||
devices = get_mqtt_devices()
|
||||
return {device['topic'].split('/')[0]: device['device_name'] for device in
|
||||
devices} # Assuming topic starts with device name
|
||||
|
||||
|
||||
def on_message(client, userdata, msg):
|
||||
"""" Simple method to handle messages """
|
||||
topic = msg.topic.split('/')
|
||||
device_name = topic[0]
|
||||
sensor = topic[-2]
|
||||
"""Handle incoming messages from MQTT broker."""
|
||||
try:
|
||||
# Parse the incoming message topic
|
||||
topic_parts = msg.topic.split('/')
|
||||
device_endpoint = topic_parts[0] # This is the actual endpoint name
|
||||
sensor_type = topic_parts[2] # Assuming sensor type is in the third part
|
||||
|
||||
if device_name not in mqtt_data:
|
||||
mqtt_data[device_name] = {"time": datetime.now(),
|
||||
"device": device_name,
|
||||
"temperature": None,
|
||||
"humidity": None}
|
||||
sensor_value = float(msg.payload.decode())
|
||||
print(f"Received message from {device_endpoint}, sensor {sensor_type}: {sensor_value}")
|
||||
|
||||
if sensor == "tempreature":
|
||||
mqtt_data[device_name]["temperature"] = float(msg.payload.decode())
|
||||
elif sensor == "humidity":
|
||||
mqtt_data[device_name]["humidity"] = float(msg.payload.decode())
|
||||
# Build the device map to get the friendly device name
|
||||
device_map = build_device_map()
|
||||
device_name = device_map.get(device_endpoint, device_endpoint) # Fallback to endpoint if not found
|
||||
|
||||
if device_name not in mqtt_data:
|
||||
mqtt_data[device_name] = {
|
||||
"time": datetime.utcnow().isoformat(),
|
||||
"device": device_name,
|
||||
"sensors": {}
|
||||
}
|
||||
|
||||
# Update the sensor value in the mqtt_data dictionary
|
||||
mqtt_data[device_name]["sensors"][sensor_type] = sensor_value
|
||||
mqtt_data[device_name]["time"] = datetime.utcnow().isoformat()
|
||||
|
||||
# Store the updated data structure in Redis
|
||||
redis_client.set(device_name, json.dumps(mqtt_data[device_name]))
|
||||
print(f"Updated data for {device_name}: {mqtt_data[device_name]}")
|
||||
|
||||
except ValueError as e:
|
||||
print(f"Error processing message payload: {e}")
|
||||
|
||||
mqtt_data[device_name]["time"] = str(datetime.now())
|
||||
redis_client.set(device_name, json.dumps(mqtt_data))
|
||||
print(f"Updated data for {device_name}: {mqtt_data[device_name]}")
|
||||
|
||||
def on_connect(client, userdata, flags, rc):
|
||||
"""Handle successful connection."""
|
||||
print(f"Connected with result code {rc}")
|
||||
devices = get_devices()
|
||||
for device in devices:
|
||||
client.subscribe(f"{device['name']}/sensor/+/state")
|
||||
"""Handle successful MQTT connection."""
|
||||
if rc == 0:
|
||||
print("Connected to MQTT Broker")
|
||||
devices = get_mqtt_devices()
|
||||
for device in devices:
|
||||
client.subscribe(device['topic']) # Subscribing to each device's topic
|
||||
print(f"Subscribed to topic: {device['topic']}")
|
||||
else:
|
||||
print(f"Failed to connect, return code {rc}")
|
||||
|
||||
|
||||
def on_disconnect(client, userdata, rc):
|
||||
"""Handle disconnection."""
|
||||
if rc != 0:
|
||||
print(f"Unexpected disconnection. Result code: {rc}")
|
||||
"""Handle disconnection from MQTT broker."""
|
||||
print(f"Disconnected with result code: {rc}")
|
||||
|
||||
|
||||
def start_mqtt_client():
|
||||
""" Start the MQTT client """
|
||||
devices = get_devices()
|
||||
|
||||
"""Start the MQTT client to begin listening to topics."""
|
||||
client = mqtt.Client()
|
||||
client.on_message = on_message
|
||||
client.on_connect = on_connect
|
||||
client.on_disconnect = on_disconnect
|
||||
client.on_message = on_message
|
||||
client.connect(MQTT_BROKER)
|
||||
|
||||
client.loop_start()
|
||||
print("MQTT Client Started")
|
||||
for device in devices:
|
||||
client.subscribe(f"{device['name']}/sensor/+/state")
|
||||
|
||||
# Keep the script running
|
||||
try:
|
||||
while True:
|
||||
time.sleep(10) # Sleep to prevent high CPU usage
|
||||
except KeyboardInterrupt:
|
||||
print("Script interrupted by user")
|
||||
finally:
|
||||
client.loop_stop() # Stop the loop when exiting
|
||||
client.loop_stop()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
start_mqtt_client()
|
||||
start_mqtt_client()
|
||||
|
||||
@@ -2,91 +2,154 @@ import json
|
||||
import datetime
|
||||
import requests
|
||||
import psycopg2
|
||||
import redis
|
||||
from django.conf import settings
|
||||
from huey import crontab
|
||||
from huey.contrib.djhuey import periodic_task
|
||||
from .models import Device
|
||||
import redis
|
||||
|
||||
from .models import Device, Sensor, SensorType
|
||||
|
||||
# Initialize Redis client
|
||||
redis_client = redis.StrictRedis(host='10.10.0.1', port=6379, db=0)
|
||||
|
||||
|
||||
def devices_to_redis():
|
||||
"""Fetch all devices from Django and store them in Redis."""
|
||||
"""Fetch devices and their sensors' topics from Django and store them in Redis."""
|
||||
devices = Device.objects.all()
|
||||
devices_list = [
|
||||
{
|
||||
'id': device.id,
|
||||
'name': device.name,
|
||||
'protocol': device.protocol,
|
||||
'ip': device.ip,
|
||||
}
|
||||
for device in devices
|
||||
]
|
||||
redis_client.set('devices', json.dumps(devices_list))
|
||||
devices_list = []
|
||||
for device in devices:
|
||||
for sensor in device.sensors.all():
|
||||
sensor_data = {
|
||||
'device_name': device.name,
|
||||
'sensor_name': sensor.type.name,
|
||||
'topic': sensor.type.topic # Assuming the topic is stored in SensorType
|
||||
}
|
||||
devices_list.append(sensor_data)
|
||||
redis_client.set('mqtt_devices', json.dumps(devices_list))
|
||||
print("Devices with sensors stored in Redis.")
|
||||
|
||||
|
||||
def fetch_data_http(device):
|
||||
"""Fetch temperature and humidity data from an HTTP sensor."""
|
||||
data = {
|
||||
"time": datetime.datetime.now().isoformat(),
|
||||
"device": device.name,
|
||||
}
|
||||
def fetch_data_http(device, sensor):
|
||||
"""Fetch data from an HTTP sensor."""
|
||||
sensor_type_name = sensor.type.name.lower()
|
||||
try:
|
||||
temperature_response = requests.get(f"http://{device.ip}/sensor/tempreature")
|
||||
humidity_response = requests.get(f"http://{device.ip}/sensor/humidity")
|
||||
data["temperature"] = temperature_response.json().get('value')
|
||||
data["humidity"] = humidity_response.json().get('value')
|
||||
# Make the request to the device's HTTP endpoint
|
||||
response = requests.get(f"http://{device.ip}/sensor/{sensor_type_name}", timeout=5)
|
||||
response.raise_for_status() # Raise an exception for any non-200 status codes
|
||||
sensor_value = response.json().get('value') # Assuming the JSON response structure
|
||||
if sensor_value is not None:
|
||||
return {
|
||||
"time": datetime.datetime.utcnow().isoformat(),
|
||||
"device": device.name,
|
||||
"sensor": sensor_type_name,
|
||||
"sensor_value": sensor_value
|
||||
}
|
||||
else:
|
||||
print(f"No value returned from {device.name} for {sensor_type_name}")
|
||||
except requests.RequestException as e:
|
||||
print(f"HTTP request failed: {e}")
|
||||
return data
|
||||
print(f"HTTP request failed for {device.name}: {e}")
|
||||
return None
|
||||
|
||||
|
||||
def fetch_data_mqtt(device_name):
|
||||
"""Fetch data from Redis for a specific MQTT device."""
|
||||
data = redis_client.get(device_name)
|
||||
def fetch_data_mqtt(device, sensor):
|
||||
"""Fetch data from Redis for a specific MQTT device and sensor."""
|
||||
# Get the data for the specific device from Redis
|
||||
data = redis_client.get(device.name) # Assumes device.name is the Redis key
|
||||
if data:
|
||||
data = json.loads(data.decode('utf-8')).get(device_name)
|
||||
if data and datetime.datetime.fromisoformat(data["time"]) > datetime.datetime.now() - datetime.timedelta(
|
||||
minutes=2):
|
||||
return data
|
||||
data = json.loads(data.decode('utf-8'))
|
||||
|
||||
# Normalize the sensor name to lowercase for lookup
|
||||
sensor_name = sensor.type.name.lower()
|
||||
sensor_value = data['sensors'].get(sensor_name)
|
||||
|
||||
if sensor_value is not None and is_recent_data(data['time']):
|
||||
return {
|
||||
"time": data['time'],
|
||||
"device": device.name,
|
||||
"sensor_value": sensor_value
|
||||
}
|
||||
|
||||
print(data)
|
||||
return None
|
||||
|
||||
|
||||
def insert_data(data):
|
||||
"""Insert data into the PostgreSQL database."""
|
||||
with psycopg2.connect(settings.CONNECTION_STRING) as conn:
|
||||
with conn.cursor() as cursor:
|
||||
insert_query = """
|
||||
INSERT INTO conditions (time, device, temperature, humidity)
|
||||
VALUES (%s, %s, %s, %s)
|
||||
"""
|
||||
cursor.execute(insert_query, (data["time"], data["device"], data["temperature"], data["humidity"]))
|
||||
conn.commit()
|
||||
def is_recent_data(timestamp):
|
||||
"""Check if data is within a 2-minute freshness window."""
|
||||
data_time = datetime.datetime.fromisoformat(timestamp)
|
||||
return data_time > datetime.datetime.utcnow() - datetime.timedelta(minutes=2)
|
||||
|
||||
def insert_data(data, sensor_type):
|
||||
"""Insert parsed data into the PostgreSQL database."""
|
||||
if 'sensor_value' not in data:
|
||||
print(f"Missing 'sensor_value' in data: {data}. Skipping insertion.")
|
||||
return
|
||||
|
||||
insert_data_dict = {
|
||||
"time": data['time'],
|
||||
"device": data['device'],
|
||||
"metric": sensor_type.lower(),
|
||||
"value": data['sensor_value'],
|
||||
}
|
||||
|
||||
try:
|
||||
with psycopg2.connect(settings.CONNECTION_STRING) as conn:
|
||||
with conn.cursor() as cursor:
|
||||
insert_query = """
|
||||
INSERT INTO sensor_readings (time, device_name, metric, value)
|
||||
VALUES (%s, %s, %s, %s);
|
||||
"""
|
||||
cursor.execute(insert_query, (
|
||||
insert_data_dict["time"],
|
||||
insert_data_dict["device"],
|
||||
insert_data_dict["metric"],
|
||||
insert_data_dict["value"]
|
||||
))
|
||||
conn.commit()
|
||||
print(f"Data inserted successfully for {insert_data_dict['device']}: {insert_data_dict}")
|
||||
except Exception as e:
|
||||
print(f"Failed to insert data: {e}")
|
||||
|
||||
@periodic_task(crontab(minute='*/1'))
|
||||
def fetch_data_from_all_devices():
|
||||
"""Fetch and insert data for all devices based on their protocol."""
|
||||
devices = Device.objects.all()
|
||||
for device in devices:
|
||||
data = None
|
||||
if device.protocol == 'http':
|
||||
data = fetch_data_http(device)
|
||||
elif device.protocol == 'mqtt':
|
||||
data = fetch_data_mqtt(device.name)
|
||||
for sensor in device.sensors.all():
|
||||
data = None
|
||||
|
||||
if data:
|
||||
data_time = datetime.datetime.fromisoformat(data["time"])
|
||||
if data_time > datetime.datetime.now() - datetime.timedelta(minutes=1):
|
||||
insert_data(data)
|
||||
if device.protocol == 'http':
|
||||
data = fetch_data_http(device, sensor)
|
||||
elif device.protocol == 'mqtt':
|
||||
data = fetch_data_mqtt(device, sensor)
|
||||
|
||||
if data and is_recent_data(data['time']):
|
||||
insert_data(data, sensor.type.name)
|
||||
else:
|
||||
print(f"No recent data available for {device.name}. Skipping insertion.")
|
||||
else:
|
||||
print(f"No data available for {device.name}. Skipping insertion.")
|
||||
print(f"No recent or valid data for {device.name}. Skipping.")
|
||||
|
||||
@periodic_task(crontab(minute='*/5'))
|
||||
def last_5_minutes():
|
||||
"""Fetch the last 5 readings from TimescaleDB and store them in Redis."""
|
||||
try:
|
||||
with psycopg2.connect(settings.CONNECTION_STRING) as conn:
|
||||
with conn.cursor() as cursor:
|
||||
cursor.execute("""
|
||||
SELECT time, device_name, metric, value
|
||||
FROM sensor_readings
|
||||
ORDER BY time DESC
|
||||
LIMIT 5;
|
||||
""")
|
||||
results = cursor.fetchall()
|
||||
|
||||
data = [
|
||||
{
|
||||
"time": reading[0].isoformat(),
|
||||
"device": reading[1],
|
||||
"metric": reading[2],
|
||||
"value": reading[3]
|
||||
}
|
||||
for reading in results
|
||||
]
|
||||
redis_client.set("last5", json.dumps(data))
|
||||
print("Last 5 readings:", data)
|
||||
except Exception as e:
|
||||
print(f"Error fetching or storing the last 5 readings: {e}")
|
||||
|
||||
# Initialize device data in Redis
|
||||
devices_to_redis()
|
||||
|
||||
@@ -1,222 +1,144 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Conditions Chart with Chart.js</title>
|
||||
<!-- Bootstrap CSS -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<title>IoT Sensor Dashboard</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
<style>
|
||||
.chart-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 400px;
|
||||
background-color: #f9f9f9;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0px 0px 20px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
#conditionsChart {
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
}
|
||||
|
||||
.current-conditions {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
.device-panel {
|
||||
margin-bottom: 20px;
|
||||
background-color: #ffffff;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0px 0px 15px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.current-conditions h2 {
|
||||
font-size: 36px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.current-conditions .value {
|
||||
font-size: 48px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="container my-4">
|
||||
<!-- GPT Summary -->
|
||||
<h4 class="text-center mb-4">{{ gpt }}</h4>
|
||||
|
||||
<body class="bg-light">
|
||||
<nav class="navbar navbar-expand-lg navbar-light bg-light">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="#">IoT Dashboard</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{% url 'chart' %}">Chart</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{% url 'device_list' %}">Devices</a>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="navbar-nav">
|
||||
{% if user.is_authenticated %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{% url 'logout' %}">Logout</a>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{% url 'login' %}">Login</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<h1 class="text-center mb-4">IoT Sensor Data Dashboard</h1>
|
||||
|
||||
<div class="container mt-5">
|
||||
<h1 class="text-center mb-4">Temperature and Humidity Over Time</h1>
|
||||
<!-- Button to add a new "island" (device panel) -->
|
||||
<div class="text-center mb-4">
|
||||
<button class="btn btn-success" onclick="addDevicePanel()">Add Device Panel</button>
|
||||
</div>
|
||||
|
||||
<!-- Current Conditions -->
|
||||
<div class="current-conditions">
|
||||
<h2>Current Conditions</h2>
|
||||
<div class="value" id="current-temperature">Loading...</div>
|
||||
<div class="value" id="current-humidity">Loading...</div>
|
||||
</div>
|
||||
|
||||
<!-- Device Selector Dropdown -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-4 offset-md-4">
|
||||
<select id="deviceSelector" class="form-select" onchange="fetchDeviceData()">
|
||||
{% for device in devices %}
|
||||
<option value="{{ device.name }}">{{ device.name }} ({{ device.ip }})</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Date Pickers for Time Window -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-6 offset-md-3 d-flex justify-content-between">
|
||||
<div>
|
||||
<label for="startDate" class="form-label">Start Date:</label>
|
||||
<input type="datetime-local" id="startDate" class="form-control" onchange="fetchDeviceData()">
|
||||
</div>
|
||||
<div>
|
||||
<label for="endDate" class="form-label">End Date:</label>
|
||||
<input type="datetime-local" id="endDate" class="form-control" onchange="fetchDeviceData()">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Chart Container -->
|
||||
<div class="row">
|
||||
<div class="col-md-8 offset-md-2">
|
||||
<div class="chart-container">
|
||||
<canvas id="conditionsChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Container for dynamic device panels ("islands") -->
|
||||
<div id="device-panels" class="row">
|
||||
<!-- Dynamic panels will be added here -->
|
||||
</div>
|
||||
|
||||
<script>
|
||||
var ctx = document.getElementById('conditionsChart').getContext('2d');
|
||||
var conditionsChart;
|
||||
const devices = {{ devices_json|safe }};
|
||||
let panelCount = 0;
|
||||
const charts = {};
|
||||
|
||||
function fetchDeviceData() {
|
||||
var device = document.getElementById('deviceSelector').value;
|
||||
var startDate = document.getElementById('startDate').value;
|
||||
var endDate = document.getElementById('endDate').value;
|
||||
// Function to add a new device panel
|
||||
function addDevicePanel() {
|
||||
const panelId = `device-panel-${panelCount}`;
|
||||
const chartId = `sensorChart-${panelCount}`;
|
||||
const panelHtml = `
|
||||
<div class="col-md-6 device-panel" id="${panelId}">
|
||||
<div class="card">
|
||||
<div class="card-header d-flex justify-content-between">
|
||||
<span>Device Panel</span>
|
||||
<button class="btn btn-danger btn-sm" onclick="removeDevicePanel('${panelId}')">Remove</button>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="mb-3">
|
||||
<label for="device-select-${panelId}" class="form-label">Choose a device:</label>
|
||||
<select id="device-select-${panelId}" class="form-select" onchange="fetchSensorData('${panelId}', '${chartId}')">
|
||||
<!-- Dynamically populated options -->
|
||||
</select>
|
||||
</div>
|
||||
|
||||
fetch(`/fetch_device_data/?device=${device}&start_date=${startDate}&end_date=${endDate}`)
|
||||
<!-- Date and Time Selection -->
|
||||
<div class="mb-3">
|
||||
<label for="start-date-${panelId}" class="form-label">Start Date and Time:</label>
|
||||
<input type="datetime-local" id="start-date-${panelId}" class="form-control">
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="end-date-${panelId}" class="form-label">End Date and Time:</label>
|
||||
<input type="datetime-local" id="end-date-${panelId}" class="form-control">
|
||||
</div>
|
||||
|
||||
<button class="btn btn-primary w-100" onclick="fetchSensorData('${panelId}', '${chartId}')">Fetch Data</button>
|
||||
|
||||
<canvas id="${chartId}" class="mt-4"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
document.getElementById('device-panels').insertAdjacentHTML('beforeend', panelHtml);
|
||||
populateDeviceSelect(`device-select-${panelId}`);
|
||||
|
||||
const ctx = document.getElementById(chartId).getContext('2d');
|
||||
charts[chartId] = new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: { labels: [], datasets: [] },
|
||||
options: { responsive: true }
|
||||
});
|
||||
|
||||
panelCount++; // Increment panelCount after using it
|
||||
}
|
||||
|
||||
// Function to populate device dropdown
|
||||
function populateDeviceSelect(selectId) {
|
||||
const deviceSelect = document.getElementById(selectId);
|
||||
deviceSelect.innerHTML = ""; // Clear previous options
|
||||
devices.forEach(device => {
|
||||
const option = document.createElement('option');
|
||||
option.value = device.name;
|
||||
option.text = `${device.name} (${device.sensors__type__name})`;
|
||||
deviceSelect.appendChild(option);
|
||||
});
|
||||
}
|
||||
|
||||
// Function to remove a device panel
|
||||
function removeDevicePanel(panelId) {
|
||||
const panel = document.getElementById(panelId);
|
||||
panel.remove();
|
||||
}
|
||||
|
||||
// Function to fetch sensor data and update the chart for a specific panel
|
||||
function fetchSensorData(panelId, chartId) {
|
||||
const device = document.getElementById(`device-select-${panelId}`).value;
|
||||
const startDate = document.getElementById(`start-date-${panelId}`).value;
|
||||
const endDate = document.getElementById(`end-date-${panelId}`).value;
|
||||
|
||||
if (!device || !startDate || !endDate) {
|
||||
alert('Please select a device and both start and end date/time.');
|
||||
return;
|
||||
}
|
||||
|
||||
fetch(`/fetch_device_data?device=${device}&start_date=${startDate}&end_date=${endDate}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (conditionsChart) {
|
||||
conditionsChart.destroy(); // Destroy the old chart
|
||||
}
|
||||
const times = Object.values(data)[0].times;
|
||||
const datasets = Object.keys(data).map(metric => ({
|
||||
label: metric,
|
||||
data: data[metric].values,
|
||||
borderColor: getRandomColor(),
|
||||
fill: false
|
||||
}));
|
||||
|
||||
conditionsChart = new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: data.times,
|
||||
datasets: [
|
||||
{
|
||||
label: 'Temperature (°C)',
|
||||
data: data.temperatures,
|
||||
borderColor: 'red',
|
||||
fill: false,
|
||||
},
|
||||
{
|
||||
label: 'Humidity (%)',
|
||||
data: data.humidities,
|
||||
borderColor: 'blue',
|
||||
fill: false,
|
||||
}
|
||||
]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
scales: {
|
||||
x: {
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Time'
|
||||
},
|
||||
ticks: {
|
||||
autoSkip: true,
|
||||
maxRotation: 45,
|
||||
minRotation: 45,
|
||||
}
|
||||
},
|
||||
y: {
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Values'
|
||||
},
|
||||
beginAtZero: true
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
display: true,
|
||||
position: 'top',
|
||||
labels: {
|
||||
boxWidth: 20,
|
||||
padding: 20,
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
enabled: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Check if the last recorded data is within the last 10 minutes
|
||||
const lastRecordedTime = new Date(data.times[data.times.length - 1]);
|
||||
const now = new Date();
|
||||
const tenMinutesAgo = new Date(now.getTime() - 10 * 60000);
|
||||
|
||||
if (lastRecordedTime > tenMinutesAgo) {
|
||||
document.getElementById('current-temperature').textContent = `Temperature: ${data.temperatures[data.temperatures.length - 1]}°C`;
|
||||
document.getElementById('current-humidity').textContent = `Humidity: ${data.humidities[data.humidities.length - 1]}%`;
|
||||
} else {
|
||||
document.getElementById('current-temperature').textContent = `Temperature: -`;
|
||||
document.getElementById('current-humidity').textContent = `Humidity: -`;
|
||||
}
|
||||
charts[chartId].data.labels = times;
|
||||
charts[chartId].data.datasets = datasets;
|
||||
charts[chartId].update();
|
||||
});
|
||||
}
|
||||
|
||||
// Initial load for the default device and time range
|
||||
fetchDeviceData();
|
||||
// Function to generate random color for chart lines
|
||||
function getRandomColor() {
|
||||
const letters = '0123456789ABCDEF';
|
||||
let color = '#';
|
||||
for (let i = 0; i < 6; i++) color += letters[Math.floor(Math.random() * 16)];
|
||||
return color;
|
||||
}
|
||||
|
||||
// Initialize with one device panel
|
||||
addDevicePanel();
|
||||
</script>
|
||||
|
||||
<!-- Bootstrap JS and dependencies (Optional) -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
@@ -5,9 +5,11 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{% if form.instance.pk %}Edit{% else %}Add{% endif %} Device</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
</head>
|
||||
|
||||
<body class="bg-light">
|
||||
<nav class="navbar navbar-expand-lg navbar-light bg-light">
|
||||
<nav class="navbar navbar-expand-lg navbar-light bg-light">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="#">IoT Dashboard</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
@@ -16,7 +18,7 @@
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{% url 'chart' %}">Chart</a>
|
||||
<a class="nav-link" href="{% url 'index' %}">Chart</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{% url 'device_list' %}">Devices</a>
|
||||
@@ -36,14 +38,29 @@
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="container mt-5">
|
||||
<h1 class="text-center mb-4">{% if form.instance.pk %}Edit{% else %}Add{% endif %} Device</h1>
|
||||
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<div class="mb-3">
|
||||
<!-- Display the form fields -->
|
||||
{{ form.as_p }}
|
||||
|
||||
<!-- If there are errors, display them -->
|
||||
{% if form.errors %}
|
||||
<div class="alert alert-danger">
|
||||
<ul>
|
||||
{% for field, errors in form.errors.items %}
|
||||
<li>{{ field }}: {{ errors|join:", " }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Submit and Cancel buttons -->
|
||||
<button type="submit" class="btn btn-success">Save</button>
|
||||
<a href="{% url 'device_list' %}" class="btn btn-secondary">Cancel</a>
|
||||
</form>
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
</head>
|
||||
<body class="bg-light">
|
||||
<nav class="navbar navbar-expand-lg navbar-light bg-light">
|
||||
<!-- Navbar -->
|
||||
<nav class="navbar navbar-expand-lg navbar-light bg-light">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="#">IoT Dashboard</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
@@ -15,30 +16,23 @@
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{% url 'chart' %}">Chart</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{% url 'device_list' %}">Devices</a>
|
||||
</li>
|
||||
<li class="nav-item"><a class="nav-link" href="{% url 'index' %}">Chart</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="{% url 'device_list' %}">Devices</a></li>
|
||||
</ul>
|
||||
<ul class="navbar-nav">
|
||||
{% if user.is_authenticated %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{% url 'logout' %}">Logout</a>
|
||||
</li>
|
||||
<li class="nav-item"><a class="nav-link" href="{% url 'logout' %}">Logout</a></li>
|
||||
{% else %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{% url 'login' %}">Login</a>
|
||||
</li>
|
||||
<li class="nav-item"><a class="nav-link" href="{% url 'login' %}">Login</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Device Management -->
|
||||
<div class="container mt-5">
|
||||
<h1 class="text-center mb-4">Manage Devices</h1>
|
||||
|
||||
<a href="{% url 'add_device' %}" class="btn btn-primary mb-3">Add Device</a>
|
||||
|
||||
<table class="table table-striped">
|
||||
@@ -47,8 +41,7 @@
|
||||
<th>Name</th>
|
||||
<th>IP Address</th>
|
||||
<th>Protocol</th>
|
||||
<th>Temperature Monitoring</th>
|
||||
<th>Humidity Monitoring</th>
|
||||
<th>Sensor Types</th> <!-- Adjusted to Sensor Types -->
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -58,16 +51,27 @@
|
||||
<td>{{ device.name }}</td>
|
||||
<td>{{ device.ip }}</td>
|
||||
<td>{{ device.protocol }}</td>
|
||||
<td>{{ device.temperature|yesno:"Yes,No" }}</td>
|
||||
<td>{{ device.humidity|yesno:"Yes,No" }}</td>
|
||||
<!-- Collect and display sensor types -->
|
||||
<td>
|
||||
{% for sensor in device.sensors.all %}
|
||||
{{ sensor.type.name }}{% if not forloop.last %}, {% endif %}
|
||||
{% empty %}
|
||||
No sensors
|
||||
{% endfor %}
|
||||
</td>
|
||||
<!-- You may further expand other sensor-related data -->
|
||||
<td>
|
||||
<a href="{% url 'edit_device' device.pk %}" class="btn btn-warning btn-sm">Edit</a>
|
||||
<a href="{% url 'delete_device' device.pk %}" class="btn btn-danger btn-sm">Delete</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="5" class="text-center">No devices found.</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
@@ -20,12 +20,13 @@ from iotDashboard import views
|
||||
|
||||
urlpatterns = [
|
||||
path('admin/', admin.site.urls),
|
||||
path('',views.index),
|
||||
path('devices_api/',views.devices_api),
|
||||
path('',views.chart,name="index"),
|
||||
path('fetch_device_data/', views.fetch_device_data, name='fetch_device_data'),
|
||||
path('chart/',views.chart,name='chart'),
|
||||
path('devices/', views.device_list, name='device_list'),
|
||||
path('devices/add/', views.add_device, name='add_device'),
|
||||
path('devices/edit/<int:pk>/', views.edit_device, name='edit_device'),
|
||||
path('devices/delete/<int:pk>/', views.delete_device, name='delete_device'),
|
||||
path('logout/', views.logout_view, name='logout'),
|
||||
path('sensor/add/',views.add_sensor_with_type,name="add_sensor_with_type")
|
||||
]
|
||||
|
||||
@@ -1,57 +1,105 @@
|
||||
from django.http import HttpResponse, request, JsonResponse
|
||||
import json
|
||||
|
||||
from django.core.serializers.json import DjangoJSONEncoder
|
||||
from django.http import JsonResponse, HttpResponse
|
||||
from django.db import connections
|
||||
from django.shortcuts import render, redirect, get_object_or_404
|
||||
from .models import Device
|
||||
from .forms import DeviceForm
|
||||
from django.views.decorators.http import require_GET
|
||||
|
||||
from .models import Device, Sensor, SensorType
|
||||
from .forms import DeviceForm, SensorWithTypeForm
|
||||
import redis
|
||||
|
||||
redis_client = redis.StrictRedis(host='10.10.0.1', port=6379, db=0)
|
||||
|
||||
|
||||
def fetch_gpt_data():
|
||||
return redis_client.get("gpt").decode("utf-8").strip('b"').replace('\\"', '"').replace("\\n", "").replace("\\", "")
|
||||
|
||||
|
||||
def chart(request):
|
||||
# Fetch devices and their related sensors
|
||||
devices = list(Device.objects.all().values('name', 'sensors__type__name'))
|
||||
|
||||
# Serialize data to JSON format
|
||||
devices_json = json.dumps(devices, cls=DjangoJSONEncoder)
|
||||
|
||||
# Pass devices data to the context
|
||||
gpt = fetch_gpt_data()
|
||||
gpt = json.loads(gpt)
|
||||
context = {'devices_json': devices_json, 'gpt': gpt["summary"]}
|
||||
|
||||
return render(request, 'chart.html', context)
|
||||
|
||||
# Fetch sensor data (AJAX)
|
||||
def fetch_device_data(request):
|
||||
device = request.GET.get('device', 'livingroom')
|
||||
device_name = request.GET.get('device', 'Livingroom')
|
||||
start_date = request.GET.get('start_date')
|
||||
end_date = request.GET.get('end_date')
|
||||
|
||||
query = """
|
||||
SELECT time, temperature, humidity
|
||||
FROM conditions
|
||||
WHERE device = %s
|
||||
"""
|
||||
params = [device]
|
||||
# Log the parameters to ensure they are correct
|
||||
print("Device Name:", device_name)
|
||||
print("Start Date:", start_date)
|
||||
print("End Date:", end_date)
|
||||
|
||||
# Get the specific device by name
|
||||
device = get_object_or_404(Device, name=device_name)
|
||||
|
||||
# Initialize the results dictionary to store sensor data
|
||||
results = {}
|
||||
|
||||
# Prepare SQL query and parameters for the specific sensor type
|
||||
query = """
|
||||
SELECT time, metric, value
|
||||
FROM sensor_readings
|
||||
WHERE device_name = %s
|
||||
"""
|
||||
params = [device.name]
|
||||
|
||||
# Add time filtering to the query
|
||||
if start_date:
|
||||
query += " AND time >= %s"
|
||||
query += " AND time >= %s::timestamptz"
|
||||
params.append(start_date)
|
||||
|
||||
if end_date:
|
||||
query += " AND time <= %s"
|
||||
query += " AND time <= %s::timestamptz"
|
||||
params.append(end_date)
|
||||
|
||||
# Log the final query and params
|
||||
print("Final Query:", query)
|
||||
print("Params Before Execution:", params)
|
||||
|
||||
# Fetch data from the database
|
||||
with connections["data"].cursor() as cursor:
|
||||
cursor.execute(query, params)
|
||||
rows = cursor.fetchall()
|
||||
|
||||
times = [row[0].strftime('%Y-%m-%d %H:%M:%S') for row in rows]
|
||||
temperatures = [row[1] for row in rows]
|
||||
humidities = [row[2] for row in rows]
|
||||
# Process the results and group them by sensor type (metric)
|
||||
for row in rows:
|
||||
time, metric, value = row
|
||||
formatted_time = time.strftime('%Y-%m-%d %H:%M:%S')
|
||||
|
||||
return JsonResponse({
|
||||
'times': times,
|
||||
'temperatures': temperatures,
|
||||
'humidities': humidities,
|
||||
})
|
||||
def chart(request):
|
||||
devices = Device.objects.all()
|
||||
context = {'devices': devices}
|
||||
return render(request, 'chart.html', context)
|
||||
if metric not in results:
|
||||
results[metric] = {
|
||||
'times': [],
|
||||
'values': []
|
||||
}
|
||||
results[metric]['times'].append(formatted_time)
|
||||
results[metric]['values'].append(value)
|
||||
|
||||
return JsonResponse(results)
|
||||
|
||||
def index(request):
|
||||
if request.user.is_authenticated:
|
||||
return redirect("/chart/")
|
||||
return HttpResponse("NOT AUTHENTICATED!!!")
|
||||
|
||||
|
||||
def device_list(request):
|
||||
devices = Device.objects.all()
|
||||
return render(request, 'device_list.html', {'devices': devices})
|
||||
|
||||
|
||||
def add_device(request):
|
||||
if request.method == 'POST':
|
||||
form = DeviceForm(request.POST)
|
||||
@@ -62,6 +110,7 @@ def add_device(request):
|
||||
form = DeviceForm()
|
||||
return render(request, 'device_form.html', {'form': form})
|
||||
|
||||
|
||||
def edit_device(request, pk):
|
||||
device = get_object_or_404(Device, pk=pk)
|
||||
if request.method == 'POST':
|
||||
@@ -73,6 +122,7 @@ def edit_device(request, pk):
|
||||
form = DeviceForm(instance=device)
|
||||
return render(request, 'device_form.html', {'form': form})
|
||||
|
||||
|
||||
def delete_device(request, pk):
|
||||
device = get_object_or_404(Device, pk=pk)
|
||||
if request.method == 'POST':
|
||||
@@ -80,5 +130,22 @@ def delete_device(request, pk):
|
||||
return redirect('device_list')
|
||||
return render(request, 'device_confirm_delete.html', {'device': device})
|
||||
|
||||
def logout_view():
|
||||
redirect("/admin")
|
||||
def add_sensor_with_type(request):
|
||||
if request.method == 'POST':
|
||||
form = SensorWithTypeForm(request.POST)
|
||||
if form.is_valid():
|
||||
form.save() # This will save both Sensor and SensorType as needed
|
||||
return redirect('device_list') # Adjust this to your specific URL name
|
||||
else:
|
||||
form = SensorWithTypeForm()
|
||||
|
||||
context = {'form': form}
|
||||
return render(request, 'sensor_form.html', context)
|
||||
|
||||
def logout_view(request):
|
||||
return redirect("/admin")
|
||||
|
||||
def devices_api(request):
|
||||
devices = list(Device.objects.all().values('name', 'sensors__type__name'))
|
||||
return JsonResponse(devices, safe=False)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user