commit dc442f29bcf234f628b3d0ea085306db34724f7d Author: Justin Eckenweber Date: Wed May 20 10:29:00 2026 +0200 switched to gitea diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c5ef7fa --- /dev/null +++ b/.gitignore @@ -0,0 +1,218 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[codz] +*$py.class + +# C extensions +*.so + +.idea/ + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py.cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +# Pipfile.lock + +# UV +# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# uv.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +# poetry.lock +# poetry.toml + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python. +# https://pdm-project.org/en/latest/usage/project/#working-with-version-control +# pdm.lock +# pdm.toml +.pdm-python +.pdm-build/ + +# pixi +# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control. +# pixi.lock +# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one +# in the .venv directory. It is recommended not to include this directory in version control. +.pixi + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# Redis +*.rdb +*.aof +*.pid + +# RabbitMQ +mnesia/ +rabbitmq/ +rabbitmq-data/ + +# ActiveMQ +activemq-data/ + +# SageMath parsed files +*.sage.py + +# Environments +.env +.envrc +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +# .idea/ + +# Abstra +# Abstra is an AI-powered process automation framework. +# Ignore directories containing user credentials, local state, and settings. +# Learn more at https://abstra.io/docs +.abstra/ + +# Visual Studio Code +# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore +# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore +# and can be added to the global gitignore or merged into this file. However, if you prefer, +# you could uncomment the following to ignore the entire vscode folder +# .vscode/ + +# Ruff stuff: +.ruff_cache/ + +# PyPI configuration file +.pypirc + +# Marimo +marimo/_static/ +marimo/_lsp/ +__marimo__/ + +# Streamlit +.streamlit/secrets.toml \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..163739c --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ +# Über das Projekt +Dieses Repository beinhält ein Backend für einen Asia Shop, welcher Umgebungsdaten sammelt, um die Bescuherzahlen zu berechnen. + +# Vorbereitung +## Anschlüsse +Benötigt wird der BME680 und Annährungsensor (RCML-0516). +1. V3V und GND wie gewohnt bei beiden Sensoren anschließen +2. BME680 SCK wird mit SCL1 verbunden, SDI mit SDA1 +3. RCML OUT geht nur unter GPIO V3V diff --git a/bme680_default.py b/bme680_default.py new file mode 100755 index 0000000..91cab7d --- /dev/null +++ b/bme680_default.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python + +import time + +import bme680 + +print("""bme680_default.py - Displays temperature, pressure, humidity, and gas. + +Press Ctrl+C to exit! + +""") + +try: + sensor = bme680.BME680(bme680.I2C_ADDR_PRIMARY) +except (RuntimeError, IOError): + sensor = bme680.BME680(bme680.I2C_ADDR_SECONDARY) + +# These calibration data can safely be commented +# out, if desired. + +print('Calibration data:') +for name in dir(sensor.calibration_data): + + if not name.startswith('_'): + value = getattr(sensor.calibration_data, name) + + if isinstance(value, int): + print('{}: {}'.format(name, value)) + +# These oversampling settings can be tweaked to +# change the balance between accuracy and noise in +# the data. + +sensor.set_humidity_oversample(bme680.OS_2X) +sensor.set_pressure_oversample(bme680.OS_4X) +sensor.set_temperature_oversample(bme680.OS_8X) +sensor.set_filter(bme680.FILTER_SIZE_3) +sensor.set_gas_status(bme680.ENABLE_GAS_MEAS) + +print('\n\nInitial reading:') +for name in dir(sensor.data): + value = getattr(sensor.data, name) + + if not name.startswith('_'): + print('{}: {}'.format(name, value)) + +sensor.set_gas_heater_temperature(320) +sensor.set_gas_heater_duration(150) +sensor.select_gas_heater_profile(0) + +# Up to 10 heater profiles can be configured, each +# with their own temperature and duration. +# sensor.set_gas_heater_profile(200, 150, nb_profile=1) +# sensor.select_gas_heater_profile(1) + +print('\n\nPolling:') +try: + while True: + if sensor.get_sensor_data(): + output = '{0:.2f} C,{1:.2f} hPa,{2:.2f} %RH'.format( + sensor.data.temperature, + sensor.data.pressure, + sensor.data.humidity) + + if sensor.data.heat_stable: + print('{0},{1} Ohms'.format( + output, + sensor.data.gas_resistance)) + + else: + print(output) + + time.sleep(1) + +except KeyboardInterrupt: + pass diff --git a/main.py b/main.py new file mode 100644 index 0000000..e10088e --- /dev/null +++ b/main.py @@ -0,0 +1,268 @@ +import sys + +import bme680 +import uvicorn +from fastapi import FastAPI +import RPi.GPIO as GPIO +import time +import threading +import pymysql +from fastapi.middleware.cors import CORSMiddleware + + +db_user = 'sensor_user' +db_password = 'dein_passwort' + +# --- Database connection setup --- +try: + # Connect to local MySQL database + connection = pymysql.connect( + host='localhost', + port=3306, + user=db_user, + password=db_password, + database='sensor_db', + ) + + cursor = connection.cursor() + # Create table if it doesn't already exist + cursor.execute(''' + CREATE TABLE IF NOT EXISTS sensor_data + ( + id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, + timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + motion BOOLEAN NOT NULL, + temperature FLOAT NOT NULL, + humidity FLOAT NOT NULL, + pressure FLOAT NOT NULL, + gas_resistance FLOAT NOT NULL + ) + ''') + +except pymysql.Error as error: + print(error) + sys.exit(1) + +# --- GPIO setup for motion sensor (RCWL-0516) --- +sensor_pin = 22 +GPIO.setmode(GPIO.BCM) +GPIO.setup(sensor_pin, GPIO.IN) + +# --- Global shared state variables --- +state = 0 +motion_detected = False # Shared state +temperature = 0 +humidity = 0 +pressure = 0 +gas_resistance = 0 + +# ------------------------------------------------------------- +# @function proxmity_loop +# @description Monitors the motion sensor continuously and updates the shared motion state. +# @returns None +# ------------------------------------------------------------- +def proxmity_loop(): + global state, motion_detected + try: + while True: + val = GPIO.input(sensor_pin) + if val == 1: + if state == 0: + print("Motion detected!") + motion_detected = True + state = 1 + else: + if state == 1: + print("Motion stopped!") + motion_detected = False + state = 0 + time.sleep(0.1) + except Exception as e: + print(f"Sensor error: {e}") + finally: + GPIO.cleanup() + + +# ------------------------------------------------------------- +# @function sensor_loop +# @description Reads data from the BME680 sensor (temperature, humidity, pressure, gas resistance) +# and updates global variables in regular intervals. +# @returns None +# ------------------------------------------------------------- +def sensor_loop(): + global temperature, humidity, pressure, gas_resistance + bme680sensor = bme680.BME680(bme680.I2C_ADDR_SECONDARY) + + bme680sensor.set_humidity_oversample(bme680.OS_2X) + bme680sensor.set_pressure_oversample(bme680.OS_4X) + bme680sensor.set_temperature_oversample(bme680.OS_8X) + bme680sensor.set_filter(bme680.FILTER_SIZE_3) + bme680sensor.set_gas_status(bme680.ENABLE_GAS_MEAS) + + bme680sensor.set_gas_heater_temperature(320) + bme680sensor.set_gas_heater_duration(150) + bme680sensor.select_gas_heater_profile(0) + + while True: + if bme680sensor.get_sensor_data(): + temperature = bme680sensor.data.temperature + pressure = bme680sensor.data.pressure + humidity = bme680sensor.data.humidity + gas_resistance = bme680sensor.data.gas_resistance + + time.sleep(1) + + +# ------------------------------------------------------------- +# @function lineare_regression +# @description Performs a simple linear regression between x and y values. +# @param {list[float]} x - The independent variable values. +# @param {list[float]} y - The dependent variable values. +# @param {list[float]} [neue_x] - New x-values for prediction (optional). +# @returns {list[float]|None} Returns the predicted y-values or None. +# ------------------------------------------------------------- +def lineare_regression(x, y, neue_x=None): + n = len(x) + + x_mean = sum(x) / n + y_mean = sum(y) / n + + sum_xy_abweichung = 0 + sum_x_quadrat_abweichung = 0 + + for i in range(n): + x_abweichung = x[i] - x_mean + y_abweichung = y[i] - y_mean + + sum_xy_abweichung += x_abweichung * y_abweichung + sum_x_quadrat_abweichung += x_abweichung * x_abweichung + + m = sum_xy_abweichung / sum_x_quadrat_abweichung if sum_x_quadrat_abweichung != 0 else 0 + b = y_mean - m * x_mean + + + vorhersagen = None + if neue_x: + vorhersagen = [round(m * xi + b, 4) for xi in neue_x] + + return vorhersagen + + +# ------------------------------------------------------------- +# @function database_loop +# @description Periodically stores the latest sensor readings into the MySQL database. +# @returns None +# ------------------------------------------------------------- +def database_loop(): + global temperature, humidity, pressure, gas_resistance, motion_detected + + time.sleep(10) + + while True: + cursor.execute('INSERT INTO sensor_data(motion, temperature, humidity, pressure, gas_resistance) VALUES (%s, %s, %s, %s, %s) ', ( + motion_detected, temperature, humidity, pressure, gas_resistance + )) + + connection.commit() + + time.sleep(300) + +# ------------------------------------------------------------- +# @function startup_event +# @description Initializes background threads for the sensor loops on application startup. +# @returns None +# ------------------------------------------------------------- +app = FastAPI() + +@app.on_event("startup") +def startup_event(): + sensor_thread = threading.Thread(target=sensor_loop, daemon=True) + sensor_thread.start() + + proxmity_thread = threading.Thread(target=proxmity_loop, daemon=True) + proxmity_thread.start() + + database_thread = threading.Thread(target=database_loop, daemon=True) + database_thread.start() + print("Sensor thread started") + +# --- CORS configuration (allows cross-origin requests) --- +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +# ------------------------------------------------------------- +# @endpoint GET / +# @description Basic test endpoint for server health check. +# @returns {object} Returns a simple hello message. +# ------------------------------------------------------------- +@app.get("/") +async def root(): + return {"message": "Hello World"} + + +# ------------------------------------------------------------- +# @endpoint GET /sensor +# @description Returns the latest live sensor readings from global variables. +# @returns {object} JSON object containing motion, temperature, humidity, pressure, and gas resistance. +# ------------------------------------------------------------- +@app.get("/sensor") +async def sensor(): + return { + "motion": motion_detected, + "temperature": temperature, + "humidity": humidity, + "pressure": pressure, + "gas_resistance": gas_resistance + } + + +# ------------------------------------------------------------- +# @endpoint GET /get/all +# @description Retrieves all stored sensor data from the database. +# @returns {list[object]} List of all sensor data records. +# ------------------------------------------------------------- +@app.get("/get/all") +async def get_all(): + cursor.execute("SELECT * FROM sensor_data") + sensor_data = cursor.fetchall() + + better_data = [] + + for row in sensor_data: + better_data.append({ + "id": row[0], + "timestamp": row[1], + "motion": row[2], + "temperature": row[3], + "humidity": row[4], + "pressure": row[5], + "gas_resistance": row[6] + }) + + return better_data + + +# ------------------------------------------------------------- +# @endpoint GET /get/regression +# @description Performs linear regression on predefined sample data (demo endpoint). +# @returns {list[float]} Predicted y-values for the sample regression. +# ------------------------------------------------------------- +@app.get("/get/regression") +async def getRegression(): + countGuest = [13, 11, 16, 21, 14, 52, 27, 18] + temps = [19.1, 18.0, 17.0, 16.1, 15.1, 23.1, 21.1, 19.9] + + return lineare_regression(countGuest, temps, [0, 60, 120]) + + +# ------------------------------------------------------------- +# @entrypoint +# @description Starts the FastAPI app server using Uvicorn. +# ------------------------------------------------------------- +if __name__ == "__main__": + uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True) \ No newline at end of file diff --git a/proximity_default.py b/proximity_default.py new file mode 100644 index 0000000..934504a --- /dev/null +++ b/proximity_default.py @@ -0,0 +1,26 @@ +import RPi.GPIO as GPIO +import time + +sensor_pin = 22 + +GPIO.setmode(GPIO.BCM) +GPIO.setup(sensor_pin, GPIO.IN) + +state = 0 + +try: + while True: + val = GPIO.input(sensor_pin) + if val == 1: + #GPIO.output(led_pin, GPIO.HIGH) + if state == 0: + print("Motion detected!") + state = 1 + else: + #GPIO.output(led_pin, GPIO.LOW) + if state == 1: + print("Motion stopped!") + state = 0 + time.sleep(0.1) +except KeyboardInterrupt: + GPIO.cleanup()