Commit 286bdbc0 authored by Gezer's avatar Gezer
Browse files

Add API endpoints for room and sensor availability; enhance InfluxDB helper methods

parent f6c65e4d
Showing with 201 additions and 18 deletions
+201 -18
......@@ -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"),
]
......@@ -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)
......@@ -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
......@@ -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:
......
@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;
}
}
......
<template>
<RouterLink :to="`/rooms/${encodeURIComponent(room)}`" class="room-card">
<div class="card-content">
<h3>{{ room }}</h3>
</div>
</RouterLink>
</template>
<script setup lang="ts">
defineProps<{
room: string
}>()
</script>
<style scoped>
.room-card {
display: block;
background: #f9f9f9;
border-radius: 8px;
text-decoration: none;
color: #333;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
transition: transform 0.2s ease;
padding: 1rem;
text-align: center;
}
.room-card:hover {
transform: translateY(-4px);
background-color: #ffeaea;
}
.card-content h3 {
margin: 0;
color: darkred;
}
</style>
......@@ -16,15 +16,14 @@
messen, die Luftqualität per LED-Farbring darzustellen und Daten kabellos zu übertragen.
</p>
<p>
Die Anzeige erfolgt farblich anhand der Luftqualität gemäß DIN EN 13779:
<p>Die Anzeige erfolgt farblich anhand der Luftqualität gemäß DIN EN 13779:</p>
<ul>
<li>Grün: gute Luftqualität (bis 800 ppm)</li>
<li>Gelb/Orange: erhöhte Werte, Lüften empfohlen</li>
<li>Rot: schlechte Luftqualität (> 1600 ppm), Lüften erforderlich</li>
<li>Blinkend Rot: sehr schlechte Luftqualität (> 2000 ppm)</li>
</ul>
</p>
<h2>Hardware & Technik</h2>
<ul>
......
{
"dependencies": {
"chart.js": "^4.4.9",
"pinia": "^3.0.1",
"vue": "^3.5.13",
"vue-chartjs": "^5.3.2",
"vue-router": "^4.5.0"
}
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment