diff --git a/backend/app/urls.py b/backend/app/urls.py
index 9198df3ba09cab90c2b3c06f78cf48441d02ede6..432ceaa5a42ef7cdba10bb353d3274b1e41901fc 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 fd2519f4a6ddcd83c2c8a70cdc9f6917a8c8bb8d..78e679b9bb4faa539b0184caf86dee7e61cbf4b8 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 f34d3b75d065fc600e570e0275db5bf392516ebd..6c2ed3d715835f58921cb3dbf062e33bc6df925d 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 a31683dab409468578760ebf0fa5a7ac496e05cf..66867d8a89696d869adf3c4a1a034832902eb288 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 90a62647db1c74a8c09d3feda0c1701699dfccd0..613d626ff1f71d6202e80c2764958e7c5171caf0 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 0000000000000000000000000000000000000000..76d1ac01c11bb1425483237e1a7a9f7a06fd3ebc
--- /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: