diff --git a/arduino/firmware.ino b/arduino/firmware.ino new file mode 100644 index 0000000..edcb6cd --- /dev/null +++ b/arduino/firmware.ino @@ -0,0 +1,97 @@ +#include +#include +#include +#include + +#define SSID "" +#define PASSWORT "" +#define API_HOST "" // Deine API-Domain +#define API_ENDPOINT "/sensor-data/" // API-Endpunkt +#define API_PORT 8080 // Falls HTTPS, dann 443 +#define CLIENT_ID "1.54" // Eindeutige ID für den Arduino +#define API_TOKEN "test2" // Setze hier dein API-Token + +Adafruit_BME680 bme; +WiFiClient client; + +void setup() { + Serial.begin(115200); + while (!Serial); + + if (WiFi.status() == WL_NO_MODULE) { + Serial.println("WiFi-Modul nicht gefunden!"); + while (1); + } + + WiFi.begin(SSID, PASSWORT); + Serial.print("Verbinde mit WLAN..."); + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + Serial.println("\nWLAN verbunden!"); + + if (!bme.begin()) { + Serial.println("BME680 nicht gefunden!"); + while (1); + } + + bme.setTemperatureOversampling(BME680_OS_8X); + bme.setHumidityOversampling(BME680_OS_2X); + bme.setPressureOversampling(BME680_OS_4X); + bme.setIIRFilterSize(BME680_FILTER_SIZE_3); + bme.setGasHeater(320, 150); +} + +void loop() { + if (!bme.performReading()) { + Serial.println("Fehler beim Auslesen des BME680!"); + return; + } + + if (WiFi.status() == WL_CONNECTED) { + Serial.println("Sende Daten an API..."); + + String jsonPayload = "{"; + jsonPayload += "\"token\": \"" + String(API_TOKEN) + "\","; + jsonPayload += "\"clientid\": \"" + String(CLIENT_ID) + "\","; + jsonPayload += "\"temperature\": " + String(bme.temperature) + ","; + jsonPayload += "\"humidity\": " + String(bme.humidity) + ","; + jsonPayload += "\"pressure\": " + String(bme.pressure / 100.0) + ","; + jsonPayload += "\"voc\": " + String(0.0) + ","; // Falls VOC nicht gemessen wird + jsonPayload += "\"gas\": " + String(bme.gas_resistance / 1000.0); + jsonPayload += "}"; + + if (client.connect(API_HOST, API_PORT)) { // Falls HTTPS genutzt wird, dann WiFiSSLClient + client.println("POST " + String(API_ENDPOINT) + " HTTP/1.1"); + client.println("Host: " + String(API_HOST)); + client.println("Content-Type: application/json"); + client.print("Content-Length: "); + client.println(jsonPayload.length()); + client.println(); + client.println(jsonPayload); + + unsigned long timeout = millis() + 5000; // Wartezeit für die Antwort + while (client.available() == 0) { + if (millis() > timeout) { + Serial.println("Timeout bei API-Antwort!"); + client.stop(); + return; + } + } + + while (client.available()) { + String response = client.readString(); + Serial.println("API Antwort: " + response); + } + } else { + Serial.println("Fehler beim Verbinden mit API!"); + } + + client.stop(); + } else { + Serial.println("WLAN nicht verbunden!"); + } + + delay(5000); +} diff --git a/db/ERMv2.png b/db/ERMv2.png new file mode 100644 index 0000000..dc1a207 Binary files /dev/null and b/db/ERMv2.png differ diff --git a/db/create_db.sql b/db/create_db.sql new file mode 100644 index 0000000..87f4fbd --- /dev/null +++ b/db/create_db.sql @@ -0,0 +1,51 @@ +/* +SETUP DATABASE ENVIROMENT FOR bbzw-horizon +INP21bL - M241/M245 +*/ + +-- CREATE TABLES + +CREATE TABLE "clients"( + id INTEGER GENERATED BY DEFAULT AS IDENTITY, + name VARCHAR(50), + PRIMARY KEY(id) +); + +CREATE TABLE "sensor_data"( + id INTEGER GENERATED BY DEFAULT AS IDENTITY, + timestamp TIMESTAMP, + humidity DECIMAL(5,3), + pressure DECIMAL(5,3), + temperature DECIMAL(5,3), + voc DECIMAL(5,3), + gas DECIMAL(5,3), + clientid INTEGER, + PRIMARY KEY(id) +); + +CREATE TABLE "user"( + id INTEGER GENERATED BY DEFAULT AS IDENTITY, + name VARCHAR(50), + mail VARCHAR(150), + password VARCHAR(250), + api_access BOOLEAN, + PRIMARY KEY(id) +); + + +CREATE TABLE "sessions"( + id INTEGER GENERATED BY DEFAULT AS IDENTITY, + token VARCHAR(96), + validuntil DATE, + userid INTEGER, + PRIMARY KEY(id) +); + + +-- SET FOREIGN KEYS +ALTER TABLE "sensor_data" ADD FOREIGN KEY(clientid) REFERENCES "clients"(id); +ALTER TABLE "sessions" ADD FOREIGN KEY(userid) REFERENCES "user"(id); + +-- SET DEFAULT VALUES +ALTER TABLE "user" ALTER COLUMN "api_access" SET DEFAULT False; +ALTER TABLE "sensor_data" ALTER COLUMN "timestamp" SET DEFAULT Now(); diff --git a/db/create_users.sql b/db/create_users.sql new file mode 100644 index 0000000..0c1c4b2 --- /dev/null +++ b/db/create_users.sql @@ -0,0 +1,8 @@ +INSERT INTO "user" ("name", "api_access") VALUES + ('test', True); + +INSERT INTO "sessions" ("token", "userid", "validuntil") VALUES + ('test2', 1, '2025-04-30'); + +INSERT INTO "clients" ("name") VALUES + ('1.54'); \ No newline at end of file diff --git a/webservice/Dockerfile.txt b/webservice/Dockerfile.txt new file mode 100644 index 0000000..40baadf --- /dev/null +++ b/webservice/Dockerfile.txt @@ -0,0 +1,25 @@ +FROM python:3.12 + +# Create directory +RUN mkdir app +WORKDIR /app/ + +# Copy files +COPY requirements.txt . + +# Set enviromental variables +ENV DB_CONNECTION_STRING ep-plannerDB + +# Install needed packages +RUN pip3 install --upgrade pip +RUN pip3 install pipenv +RUN pip install --no-cache-dir -r requirements.txt + +# Expose port +EXPOSE 8080 + +# Imort script +COPY webservice.py . + +# Start app +CMD ["uvicorn", "webservice:app", "--host", "0.0.0.0", "--port", "8080"] \ No newline at end of file diff --git a/webservice/requirements.txt b/webservice/requirements.txt new file mode 100644 index 0000000..7cae057 --- /dev/null +++ b/webservice/requirements.txt @@ -0,0 +1,5 @@ +fastapi +uvicorn +sqlalchemy +psycopg2-binary + diff --git a/webservice/webservice.py b/webservice/webservice.py new file mode 100644 index 0000000..3a88f04 --- /dev/null +++ b/webservice/webservice.py @@ -0,0 +1,94 @@ +from fastapi import FastAPI, Depends, HTTPException +from sqlalchemy import Column, Integer, Float, String, DateTime, create_engine, ForeignKey +from sqlalchemy.orm import sessionmaker, declarative_base, Session, relationship +from datetime import datetime +import os + +DATABASE_URL = os.getenv("DB_CONNECTION_STRING", "postgresql://user:password@localhost/sensordb") + +Base = declarative_base() +engine = create_engine(DATABASE_URL) +SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) + +app = FastAPI() + +# Neue Clients-Tabelle +class Client(Base): + __tablename__ = "clients" + + id = Column(Integer, primary_key=True, index=True) + name = Column(String, unique=True, index=True) + +class SensorData(Base): + __tablename__ = "sensor_data" + + id = Column(Integer, primary_key=True, index=True) + clientid = Column(Integer, ForeignKey("clients.id"), index=True) + timestamp = Column(DateTime, default=datetime.utcnow) + temperature = Column(Float) + humidity = Column(Float) + pressure = Column(Float) + voc = Column(Float) + gas = Column(Float) + + client = relationship("Client") + +class SessionToken(Base): + __tablename__ = "sessions" + + id = Column(Integer, primary_key=True, index=True) + token = Column(String, unique=True, index=True) + validuntil = Column(DateTime) + userid = Column(Integer, ForeignKey("user.id")) + +def get_db(): + db = SessionLocal() + try: + yield db + finally: + db.close() + +def verify_token(token: str, db: Session): + session = db.query(SessionToken).filter(SessionToken.token == token).first() + if not session or session.validuntil < datetime.utcnow(): + raise HTTPException(status_code=401, detail="Invalid or expired token") + return session + +@app.post("/sensor-data/") +def receive_sensor_data( + token: str, + clientname: str, # Ändert clientid zu clientname + temperature: float, + humidity: float, + pressure: float, + voc: float, + gas: float, + db: Session = Depends(get_db) +): + verify_token(token, db) + + # Suche die Client-ID anhand des Client-Namens + client = db.query(Client).filter(Client.name == clientname).first() + if not client: + raise HTTPException(status_code=404, detail="Client not found") + + sensor_data = SensorData( + clientid=client.id, # Verwende die gefundene ID + temperature=temperature, + humidity=humidity, + pressure=pressure, + voc=voc, + gas=gas + ) + db.add(sensor_data) + db.commit() + db.refresh(sensor_data) + return {"message": "Data received", "id": sensor_data.id} + +@app.get("/sensor-data/") +def get_sensor_data(db: Session = Depends(get_db)): + data = db.query(SensorData).all() + return data + +# Erstelle die Tabellen, falls sie noch nicht existieren +Base.metadata.create_all(bind=engine)