From 56935db4d505bc05e8bf25e81e38167f6a6795bc Mon Sep 17 00:00:00 2001 From: ferdzo Date: Thu, 10 Oct 2024 12:58:04 +0200 Subject: [PATCH] Updates --- iotDashboard/forms.py | 28 +- iotDashboard/tasks.py | 13 +- iotDashboard/templates/chart.html | 356 ++++++++++++------ iotDashboard/templates/device_form.html | 12 +- .../templates/sensor_confirm_delete.html | 48 +++ iotDashboard/templates/sensor_form.html | 3 +- iotDashboard/templates/sensor_list.html | 64 ++++ iotDashboard/urls.py | 11 +- iotDashboard/views.py | 129 ++++++- 9 files changed, 519 insertions(+), 145 deletions(-) create mode 100644 iotDashboard/templates/sensor_confirm_delete.html create mode 100644 iotDashboard/templates/sensor_list.html diff --git a/iotDashboard/forms.py b/iotDashboard/forms.py index 8c09f2e..9648423 100644 --- a/iotDashboard/forms.py +++ b/iotDashboard/forms.py @@ -1,26 +1,14 @@ from django import forms 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 DeviceForm(forms.ModelForm): class Meta: model = Device - fields = ['name', 'ip', 'protocol'] + fields = ['name', 'ip', 'protocol'] # Exclude sensors from the fields 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 + # No need to handle sensors in the form super(DeviceForm, self).__init__(*args, **kwargs) def save(self, commit=True): @@ -29,7 +17,6 @@ class DeviceForm(forms.ModelForm): if commit: device.save() - self.save_m2m() # Ensure M2M save happens return device class SensorWithTypeForm(forms.ModelForm): @@ -45,7 +32,11 @@ class SensorWithTypeForm(forms.ModelForm): class Meta: model = Sensor - fields = ['device', 'enabled'] + fields = ['enabled'] # Exclude 'device' from the form fields + + def __init__(self, *args, **kwargs): + self.device = kwargs.pop('device', None) # Get the device from kwargs + super(SensorWithTypeForm, self).__init__(*args, **kwargs) def save(self, commit=True): # Create or get the SensorType @@ -65,8 +56,9 @@ class SensorWithTypeForm(forms.ModelForm): # Create Sensor with the SensorType found or created sensor = super(SensorWithTypeForm, self).save(commit=False) sensor.type = sensor_type + sensor.device = self.device # Associate the sensor with the device if commit: sensor.save() - return sensor \ No newline at end of file + return sensor diff --git a/iotDashboard/tasks.py b/iotDashboard/tasks.py index 44e29ab..874451f 100644 --- a/iotDashboard/tasks.py +++ b/iotDashboard/tasks.py @@ -11,6 +11,7 @@ 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 devices and their sensors' topics from Django and store them in Redis.""" devices = Device.objects.all() @@ -26,6 +27,7 @@ def devices_to_redis(): redis_client.set('mqtt_devices', json.dumps(devices_list)) print("Devices with sensors stored in Redis.") + def fetch_data_http(device, sensor): """Fetch data from an HTTP sensor.""" sensor_type_name = sensor.type.name.lower() @@ -47,6 +49,7 @@ def fetch_data_http(device, sensor): print(f"HTTP request failed for {device.name}: {e}") return None + 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 @@ -70,9 +73,10 @@ def fetch_data_mqtt(device, sensor): def is_recent_data(timestamp): - """Check if data is within a 2-minute freshness window.""" + """Check if data is within a 1-minute freshness window.""" data_time = datetime.datetime.fromisoformat(timestamp) - return data_time > datetime.datetime.utcnow() - datetime.timedelta(minutes=2) + return data_time > datetime.datetime.utcnow() - datetime.timedelta(minutes=1) + def insert_data(data, sensor_type): """Insert parsed data into the PostgreSQL database.""" @@ -105,7 +109,8 @@ def insert_data(data, sensor_type): except Exception as e: print(f"Failed to insert data: {e}") -@periodi c_task(crontab(minute='*/1')) + +@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() @@ -123,6 +128,7 @@ def fetch_data_from_all_devices(): else: 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.""" @@ -151,5 +157,6 @@ def last_5_minutes(): except Exception as e: print(f"Error fetching or storing the last 5 readings: {e}") + # Initialize device data in Redis devices_to_redis() diff --git a/iotDashboard/templates/chart.html b/iotDashboard/templates/chart.html index 2079096..04a260e 100644 --- a/iotDashboard/templates/chart.html +++ b/iotDashboard/templates/chart.html @@ -3,142 +3,280 @@ - IoT Sensor Dashboard - + IoT Dashboard + + + + + + + - - -

{{ gpt }}

+ -

IoT Sensor Data Dashboard

- - -
- + + - -
- + +
+

GPT Summary from All the Environment's Sensors

+
+ + +
+
+
+
Modular Sensor Graphs with Current Parameters
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+ + + +
+
+
GPT Recommendations for Parameters
+

+
+
- + }); + diff --git a/iotDashboard/templates/device_form.html b/iotDashboard/templates/device_form.html index e296695..485a0b6 100644 --- a/iotDashboard/templates/device_form.html +++ b/iotDashboard/templates/device_form.html @@ -46,7 +46,9 @@ {% csrf_token %}
- {{ form.as_p }} + {{ form.name.label_tag }} {{ form.name }}
+ {{ form.ip.label_tag }} {{ form.ip }}
+ {{ form.protocol.label_tag }} {{ form.protocol }}
{% if form.errors %} @@ -64,6 +66,14 @@ Cancel + + +
+ {% if form.instance.pk %} + Add Sensor + Edit Sensors + {% endif %} +
diff --git a/iotDashboard/templates/sensor_confirm_delete.html b/iotDashboard/templates/sensor_confirm_delete.html new file mode 100644 index 0000000..1867eb6 --- /dev/null +++ b/iotDashboard/templates/sensor_confirm_delete.html @@ -0,0 +1,48 @@ + + + + + + Confirm Delete Sensor + + + + + + + +
+

Confirm Delete Sensor

+
+ Warning! Are you sure you want to delete the sensor "{{ sensor.type.name }}"? This action cannot be undone. +
+
+ {% csrf_token %} +
+ + Cancel +
+
+
+ + diff --git a/iotDashboard/templates/sensor_form.html b/iotDashboard/templates/sensor_form.html index 572d018..ff1c4db 100644 --- a/iotDashboard/templates/sensor_form.html +++ b/iotDashboard/templates/sensor_form.html @@ -23,6 +23,7 @@
{% csrf_token %} {{ form.as_p }} + Cancel
@@ -38,4 +39,4 @@ {% endif %}
- \ No newline at end of file + diff --git a/iotDashboard/templates/sensor_list.html b/iotDashboard/templates/sensor_list.html new file mode 100644 index 0000000..dcf9182 --- /dev/null +++ b/iotDashboard/templates/sensor_list.html @@ -0,0 +1,64 @@ + + + + + + Manage Sensors for {{ device.name }} + + + + + + + +
+

Sensors for {{ device.name }}

+ + + + + + + + + + {% for sensor in sensors %} + + + + + + {% empty %} + + + + {% endfor %} + +
Sensor TypeEnabledActions
{{ sensor.type.name }}{{ sensor.enabled }} + Edit + Delete +
No sensors found for this device.
+ +
+ + diff --git a/iotDashboard/urls.py b/iotDashboard/urls.py index 2c4e263..f34abef 100644 --- a/iotDashboard/urls.py +++ b/iotDashboard/urls.py @@ -20,13 +20,18 @@ from iotDashboard import views urlpatterns = [ path('admin/', admin.site.urls), - path('devices_api/',views.devices_api), - path('',views.chart,name="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('devices/', views.device_list, name='device_list'), path('devices/add/', views.add_device, name='add_device'), path('devices/edit//', views.edit_device, name='edit_device'), path('devices/delete//', 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") + path('sensors//', views.sensor_list, name='sensor_list'), + path('sensor/add/', views.add_sensor_with_type, name="add_sensor_with_type"), + # path('devices//sensors/', views.sensor_list, name='sensor_list'), + path('device//add_sensor/', views.add_sensor, name='add_sensor'), + path('sensor/edit//', views.edit_sensor, name='edit_sensor'), + path('sensor/delete//', views.delete_sensor, name='delete_sensor'), ] diff --git a/iotDashboard/views.py b/iotDashboard/views.py index 11ae9a6..04e1b7d 100644 --- a/iotDashboard/views.py +++ b/iotDashboard/views.py @@ -1,14 +1,13 @@ 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 django.views.decorators.http import require_GET - -from .models import Device, Sensor, SensorType -from .forms import DeviceForm, SensorWithTypeForm import redis +from django.core.serializers.json import DjangoJSONEncoder +from django.db import connections +from django.http import JsonResponse, HttpResponse +from django.shortcuts import render, redirect, get_object_or_404 + +from .forms import DeviceForm, SensorWithTypeForm +from .models import Device, Sensor redis_client = redis.StrictRedis(host='10.10.0.1', port=6379, db=0) @@ -27,18 +26,78 @@ def chart(request): # Pass devices data to the context gpt = fetch_gpt_data() gpt = json.loads(gpt) - context = {'devices_json': devices_json, 'gpt': gpt["summary"]} + context = {'devices_json': devices_json, 'gpt': gpt} return render(request, 'chart.html', context) + # Fetch sensor data (AJAX) +# def fetch_device_data(request): +# device_name = request.GET.get('device', 'Livingroom') +# start_date = request.GET.get('start_date') +# end_date = request.GET.get('end_date') +# +# # 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::timestamptz" +# params.append(start_date) +# +# if end_date: +# 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() +# +# # 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') +# +# if metric not in results: +# results[metric] = { +# 'times': [], +# 'values': [] +# } +# results[metric]['times'].append(formatted_time) +# results[metric]['values'].append(value) +# +# return JsonResponse(results) + def fetch_device_data(request): device_name = request.GET.get('device', 'Livingroom') + sensor_name = request.GET.get('sensor') # Optional parameter for a specific sensor start_date = request.GET.get('start_date') end_date = request.GET.get('end_date') # Log the parameters to ensure they are correct print("Device Name:", device_name) + print("Sensor Name:", sensor_name) # Log sensor name print("Start Date:", start_date) print("End Date:", end_date) @@ -48,7 +107,7 @@ def fetch_device_data(request): # Initialize the results dictionary to store sensor data results = {} - # Prepare SQL query and parameters for the specific sensor type + # Prepare SQL query and parameters for the device query = """ SELECT time, metric, value FROM sensor_readings @@ -56,6 +115,11 @@ def fetch_device_data(request): """ params = [device.name] + # If a specific sensor is specified, filter by that sensor + if sensor_name: + query += " AND metric = %s" + params.append(sensor_name) + # Add time filtering to the query if start_date: query += " AND time >= %s::timestamptz" @@ -89,6 +153,7 @@ def fetch_device_data(request): return JsonResponse(results) + def index(request): if request.user.is_authenticated: return redirect("/chart/") @@ -130,6 +195,7 @@ def delete_device(request, pk): return redirect('device_list') return render(request, 'device_confirm_delete.html', {'device': device}) + def add_sensor_with_type(request): if request.method == 'POST': form = SensorWithTypeForm(request.POST) @@ -142,10 +208,53 @@ def add_sensor_with_type(request): 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) + +def sensor_list(request, device_id): + device = get_object_or_404(Device, id=device_id) + sensors = device.sensors.all() # Get sensors for this specific device + return render(request, 'sensor_list.html', {'device': device, 'sensors': sensors}) + + +def edit_sensor(request, pk): + sensor = get_object_or_404(Sensor, pk=pk) + if request.method == 'POST': + form = SensorWithTypeForm(request.POST, instance=sensor) + if form.is_valid(): + form.save() + return redirect('sensor_list', device_id=sensor.device.pk) + else: + form = SensorWithTypeForm(instance=sensor) + return render(request, 'sensor_form.html', {'form': form}) + + +def delete_sensor(request, pk): + sensor = get_object_or_404(Sensor, pk=pk) + if request.method == 'POST': + device_id = sensor.device.pk + sensor.delete() + return redirect('sensor_list', device_id=device_id) + return render(request, 'sensor_confirm_delete.html', {'sensor': sensor}) + + +def add_sensor(request, device_id): + device = get_object_or_404(Device, pk=device_id) + if request.method == 'POST': + form = SensorWithTypeForm(request.POST) + if form.is_valid(): + sensor = form.save(commit=False) + sensor.device = device # Associate the sensor with the device + sensor.save() + return redirect('device_list') # Redirect to device list or appropriate view + else: + form = SensorWithTypeForm() + + return render(request, 'sensor_form.html', {'form': form, 'device': device})