From 286bdbc0de467fa9d01bac9cb675c1ea7ea5570a Mon Sep 17 00:00:00 2001
From: 21geem1bif <21geem1bif@hft-stuttgart.de>
Date: Wed, 25 Jun 2025 16:18:24 +0200
Subject: [PATCH] Add API endpoints for room and sensor availability; enhance
InfluxDB helper methods
---
backend/app/urls.py | 4 ++
backend/app/views.py | 76 +++++++++++++++++++++++++
backend/utils/influx.py | 66 +++++++++++++++++++++
docker-compose.yaml | 8 +--
frontend/src/assets/main.css | 14 ++---
frontend/src/components/SensorLight.vue | 37 ++++++++++++
frontend/src/views/AboutView.vue | 5 +-
package.json | 9 +++
8 files changed, 201 insertions(+), 18 deletions(-)
create mode 100644 frontend/src/components/SensorLight.vue
create mode 100644 package.json
diff --git a/backend/app/urls.py b/backend/app/urls.py
index 9198df3..432ceaa 100644
--- a/backend/app/urls.py
+++ b/backend/app/urls.py
@@ -13,4 +13,8 @@ urlpatterns = [
path("api/room_data_csv_download", views.room_data_csv_download, name="room_data_csv_download"),
path("api/get_rooms_from_building", views.get_rooms_from_building, name="get_rooms_from_building"),
path("api/get_bau", views.get_bau, name="get_bau"),
+ path("api/room_availability", views.room_availability, name="room_availability"),
+ path("api/sensor_availability", views.sensor_availability, name="sensor_availability"),
+ path("api/all_rooms_availability", views.all_rooms_availability, name="all_rooms_availability"),
+ path("api/all_sensors_availability", views.all_sensors_availability, name="all_sensors_availability"),
]
diff --git a/backend/app/views.py b/backend/app/views.py
index fd2519f..78e679b 100644
--- a/backend/app/views.py
+++ b/backend/app/views.py
@@ -233,3 +233,79 @@ def room_data_csv_download(request):
response = HttpResponse(csv_data, content_type="text/csv")
response["Content-Disposition"] = f'attachment; filename="{room}_data.csv"'
return response
+
+@require_http_methods(["GET"])
+def room_availability(request):
+ room = request.GET.get("room")
+ start = request.GET.get("start", "-30d")
+ stop = request.GET.get("stop", "now()")
+
+ if not room:
+ return JsonResponse({"error": "Missing 'room' parameter"}, status=400)
+
+ client = InfluxDBHelper(
+ url=os.getenv("INFLUXDB_URL"),
+ token=os.getenv("INFLUXDB_TOKEN"),
+ org=os.getenv("INFLUXDB_ORG"),
+ bucket=os.getenv("INFLUXDB_BUCKET"),
+ )
+
+ availability = client.has_room_sent_data(room, start, stop)
+
+ return JsonResponse({"room": room, "available": availability}, status=200)
+
+
+@require_http_methods(["GET"])
+def sensor_availability(request):
+ sensor_mac = request.GET.get("mac")
+ start = request.GET.get("start", "-30d")
+ stop = request.GET.get("stop", "now()")
+
+ if not sensor_mac:
+ return JsonResponse({"error": "Missing 'mac' parameter"}, status=400)
+
+ client = InfluxDBHelper(
+ url=os.getenv("INFLUXDB_URL"),
+ token=os.getenv("INFLUXDB_TOKEN"),
+ org=os.getenv("INFLUXDB_ORG"),
+ bucket=os.getenv("INFLUXDB_BUCKET"),
+ )
+
+ availability = client.has_sensor_sent_data(sensor_mac, start, stop)
+
+ return JsonResponse({"sensor_mac": sensor_mac, "available": availability}, status=200)
+
+
+@require_http_methods(["GET"])
+def all_rooms_availability(request):
+ start = request.GET.get("start", "-30d")
+ stop = request.GET.get("stop", "now()")
+
+ client = InfluxDBHelper(
+ url=os.getenv("INFLUXDB_URL"),
+ token=os.getenv("INFLUXDB_TOKEN"),
+ org=os.getenv("INFLUXDB_ORG"),
+ bucket=os.getenv("INFLUXDB_BUCKET"),
+ )
+
+ rooms_available = client.rooms_with_available_data(start, stop)
+
+ return JsonResponse({"available_rooms": rooms_available}, status=200)
+
+
+@require_http_methods(["GET"])
+def all_sensors_availability(request):
+ start = request.GET.get("start", "-30d")
+ stop = request.GET.get("stop", "now()")
+
+ client = InfluxDBHelper(
+ url=os.getenv("INFLUXDB_URL"),
+ token=os.getenv("INFLUXDB_TOKEN"),
+ org=os.getenv("INFLUXDB_ORG"),
+ bucket=os.getenv("INFLUXDB_BUCKET"),
+ )
+
+ sensors_available = client.sensors_with_available_data(start, stop)
+
+ return JsonResponse({"available_sensors": sensors_available}, status=200)
+
diff --git a/backend/utils/influx.py b/backend/utils/influx.py
index f34d3b7..6c2ed3d 100644
--- a/backend/utils/influx.py
+++ b/backend/utils/influx.py
@@ -91,5 +91,71 @@ class InfluxDBHelper:
|> yield(name: "mean")
'''
return self.query_api.query(org=self.org, query=query)
+
+ # Prüft, ob ein bestimmter Raum in einem Zeitraum Daten gesendet hat
+ def has_room_sent_data(self, room_id: str, start: str = "-30d", stop: str = "now()") -> bool:
+ query = f'''
+ from(bucket: "{self.bucket}")
+ |> range(start: {start}, stop: {stop})
+ |> filter(fn: (r) => r["_measurement"] == "sensor_data")
+ |> filter(fn: (r) => r["room"] == "{room_id}")
+ |> filter(fn: (r) => r["_field"] == "co2" or r["_field"] == "humidity" or r["_field"] == "temperature")
+ |> limit(n:1)
+ '''
+ result = self.query_api.query(org=self.org, query=query)
+ return len(result) > 0
+
+ # Prüft, ob ein bestimmter Sensor (MAC-Adresse) Daten gesendet hat
+ def has_sensor_sent_data(self, mac_address: str, start: str = "-30d", stop: str = "now()") -> bool:
+ query = f'''
+ from(bucket: "{self.bucket}")
+ |> range(start: {start}, stop: {stop})
+ |> filter(fn: (r) => r["_measurement"] == "sensor_data")
+ |> filter(fn: (r) => r["mac"] == "{mac_address}")
+ |> filter(fn: (r) => r["_field"] == "co2" or r["_field"] == "humidity" or r["_field"] == "temperature")
+ |> limit(n:1)
+ '''
+ result = self.query_api.query(org=self.org, query=query)
+ return len(result) > 0
+ # Prüft alle Räume auf Verfügbarkeit in einem Zeitraum und gibt Räume zurück, die Daten gesendet haben
+ def rooms_with_available_data(self, start: str = "-30d", stop: str = "now()") -> list:
+ query = f'''
+ import "influxdata/influxdb/schema"
+ schema.tagValues(
+ bucket: "{self.bucket}",
+ tag: "{self.TAG_ROOM}",
+ predicate: (r) => r["_measurement"] == "{self.MEASUREMENT_NAME}",
+ start: {start},
+ stop: {stop}
+ )
+ '''
+ rooms_result = self.query_api.query(org=self.org, query=query)
+ rooms = [record.get_value() for table in rooms_result for record in table.records]
+
+ available_rooms = []
+ for room in rooms:
+ if self.has_room_sent_data(room, start, stop):
+ available_rooms.append(room)
+ return available_rooms
+
+ # Prüft alle Sensoren auf Verfügbarkeit in einem Zeitraum und gibt MAC-Adressen zurück, die Daten gesendet haben
+ def sensors_with_available_data(self, start: str = "-30d", stop: str = "now()") -> list:
+ query = f'''
+ import "influxdata/influxdb/schema"
+ schema.tagValues(
+ bucket: "{self.bucket}",
+ tag: "{self.TAG_MAC}",
+ predicate: (r) => r["_measurement"] == "{self.MEASUREMENT_NAME}",
+ start: {start},
+ stop: {stop}
+ )
+ '''
+ sensors_result = self.query_api.query(org=self.org, query=query)
+ sensors = [record.get_value() for table in sensors_result for record in table.records]
+ available_sensors = []
+ for mac in sensors:
+ if self.has_sensor_sent_data(mac, start, stop):
+ available_sensors.append(mac)
+ return available_sensors
diff --git a/docker-compose.yaml b/docker-compose.yaml
index a31683d..66867d8 100644
--- a/docker-compose.yaml
+++ b/docker-compose.yaml
@@ -28,6 +28,7 @@ services:
- "8000:8000"
volumes:
- ./backend:/app
+ - /app/.venv
frontend:
build:
@@ -56,13 +57,6 @@ services:
stdin_open: true
tty: true
- mqttx-web:
- image: emqx/mqttx-web
- container_name: mqttx-web
- ports:
- - "8081:80" # Port 80 im Container auf 8081 lokal mappen
- restart: unless-stopped
-
influxdb2:
image: influxdb:2
ports:
diff --git a/frontend/src/assets/main.css b/frontend/src/assets/main.css
index 90a6264..613d626 100644
--- a/frontend/src/assets/main.css
+++ b/frontend/src/assets/main.css
@@ -1,23 +1,21 @@
@import './base.css';
#app {
- max-width: 1280px;
- margin: 0 auto;
- padding: 2rem;
+ max-width: 100%;
font-weight: normal;
+ margin-top: 30px;
+ margin-left: auto;
+ margin-right: auto;
}
-a,
-.green {
- text-decoration: none;
- color: hsla(160, 100%, 37%, 1);
+a {
transition: 0.4s;
padding: 3px;
}
@media (hover: hover) {
a:hover {
- background-color: hsla(160, 100%, 37%, 0.2);
+ text-decoration: underline;
}
}
diff --git a/frontend/src/components/SensorLight.vue b/frontend/src/components/SensorLight.vue
new file mode 100644
index 0000000..76d1ac0
--- /dev/null
+++ b/frontend/src/components/SensorLight.vue
@@ -0,0 +1,37 @@
+
+ {{ room }}
+
- Die Anzeige erfolgt farblich anhand der Luftqualität gemäß DIN EN 13779: +
Die Anzeige erfolgt farblich anhand der Luftqualität gemäß DIN EN 13779: