diff --git a/.env.example b/.env.example index 47cb514..00bbb29 100644 --- a/.env.example +++ b/.env.example @@ -10,4 +10,5 @@ DEBUG=True BACKEND_PORT=5000 HOST='0.0.0.0' USE_RELOADER=True -LOG_LEVEL=Info \ No newline at end of file +LOG_LEVEL=Info +TARGET=dev \ No newline at end of file diff --git a/backend/Dockerfile b/backend/Dockerfile index 0981ed5..c9feec1 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,6 +1,8 @@ # Uses Python 3.9 Slim which do not include all standard Libraries. If any additional standard libraries are needed, they need to be installed separately FROM python:3.9-slim +ARG TARGETENV=dev + # Installs tools that is needed for building the packages and compile pyhton dependecies RUN apt-get update && apt-get install -y \ build-essential \ @@ -8,9 +10,26 @@ RUN apt-get update && apt-get install -y \ gcc \ && rm -rf /var/lib/apt/lists/* +RUN if [ "${TARGETENV}" = "prod" ]; then \ + apt-get update && apt-get install -y \ + cmake \ + python3-opencv \ + libgpiod2 \ + libgpiod-dev \ + libcamera-dev \ + v4l-utils \ + libopencv-dev \ + libv4l-dev \ + libcamera-tools \ + libavcodec-dev \ + libavformat-dev \ + libswscale-dev \ + && rm -rf /var/lib/apt/lists/*; \ + fi + # Upgrade pip and install dependencies RUN pip install --upgrade pip -COPY requirements.txt /app/requirements.txt +COPY requirements.${TARGETENV}.txt /app/requirements.txt RUN pip install -r /app/requirements.txt # Set the working directory inside the container diff --git a/backend/requirements.dev.txt b/backend/requirements.dev.txt new file mode 100644 index 0000000..6cf5a3a --- /dev/null +++ b/backend/requirements.dev.txt @@ -0,0 +1,4 @@ +flask +flask-cors +python-dotenv +opencv-python \ No newline at end of file diff --git a/backend/requirements.prod.txt b/backend/requirements.prod.txt new file mode 100644 index 0000000..9f47cb3 --- /dev/null +++ b/backend/requirements.prod.txt @@ -0,0 +1,7 @@ +flask +flask-cors +gpiod +python-dotenv +opencv-python +dlib +face-recognition \ No newline at end of file diff --git a/backend/requirements.txt b/backend/requirements.txt deleted file mode 100644 index b8005da..0000000 --- a/backend/requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -flask -flask-cors -RPi.GPIO -python-dotenv \ No newline at end of file diff --git a/backend/src/app/__init__.py b/backend/src/app/__init__.py index 18ff085..e4ddb14 100644 --- a/backend/src/app/__init__.py +++ b/backend/src/app/__init__.py @@ -3,6 +3,7 @@ from flask import Flask from flask_cors import CORS from dotenv import load_dotenv +from app.services.camera_service import CameraService def create_app(): load_dotenv() @@ -18,6 +19,13 @@ def create_app(): from app.routes import main_routes app.register_blueprint(main_routes) + """ camera_service = CameraService() + camera_service.test_camera_feed() """ + + """ @app.teardown_appcontext + def cleanup(exception=None): + camera_service.release_camera() + """ return app def config_logger(app, log_Level='ERROR'): @@ -31,3 +39,4 @@ def config_logger(app, log_Level='ERROR'): console_handler.setFormatter(formatter) app.logger.addHandler(console_handler) + diff --git a/backend/src/app/config.py b/backend/src/app/config.py index c7a9a91..a425520 100644 --- a/backend/src/app/config.py +++ b/backend/src/app/config.py @@ -4,8 +4,9 @@ class Config: """ Base configuration Class """ - DEBUG = os.getenv('DEBUG', 'False') == 'True' + DEBUG = os.getenv('DEBUG', 'False').upper() == 'TRUE' HOST = os.getenv('HOST', '127.0.0.1') - PORT = os.getenv('BACKEND_PORT') - USE_RELOADER = os.getenv('USE_RELOADER') == 'True' + PORT = os.getenv('BACKEND_PORT', 5000) + USE_RELOADER = os.getenv('USE_RELOADER').upper() == 'TRUE' LOG_LEVEL = os.getenv('LOG_LEVEL', 'ERROR').upper() + TARGET = os.getenv('TARGET', 'dev') diff --git a/backend/src/app/routes.py b/backend/src/app/routes.py index 5d2e2c3..02007e6 100644 --- a/backend/src/app/routes.py +++ b/backend/src/app/routes.py @@ -1,4 +1,4 @@ -from flask import Blueprint +from flask import Blueprint, jsonify, Response from app.services.gpio_service import turn_led_off, turn_led_on from app.utils.response_util import create_response @@ -34,4 +34,14 @@ def led_off(): turn_led_off() return create_response("LED ist jetzt aus", "warning") except Exception as e: - return create_response(f"Error: {str(e)}", "error", 500) \ No newline at end of file + return create_response(f"Error: {str(e)}", "error", 500) + +""" @main_routes.route('/capture', methods=['GET']) +def capture_image(): + try: + frame = camera_service.capture_frame() + # Encode image to JPEG + _, jpeg = cv2.imencode('.jpg', frame) + return Response(jpeg.tobytes(), mimetype='image/jpeg') + except RuntimeError as e: + return jsonify({'error': str(e)}), 500 """ \ No newline at end of file diff --git a/backend/src/app/services/camera_service.py b/backend/src/app/services/camera_service.py new file mode 100644 index 0000000..fe919fe --- /dev/null +++ b/backend/src/app/services/camera_service.py @@ -0,0 +1,38 @@ +import cv2 + +class CameraService: + def __init__(self): + self.cap = cv2.VideoCapture('/dev/video0', cv2.CAP_V4L2) + + if not self.cap.isOpened(): + raise RuntimeError("Error: Could not open camera.") + + self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1920) + self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 1080) + self.cap.set(cv2.CAP_PROP_FPS, 30) + + print("Camera initialized successfully") + + def capture_frame(self): + ret, frame = self.cap.read() + if not ret: + raise RuntimeError("Failed to capture frame from camera") + return frame + + def release_camera(self): + self.cap.release() + print("Camera released") + + def test_camera_feed(self): + while True: + frame = self.capture_frame() + + cv2.imshow("Camera Feed", frame) + + if cv2.waitKey(1) & 0xFF == ord('q'): + break + + self.release_camera() + cv2.destroyAllWindows() + + diff --git a/backend/src/app/services/gpio_service.py b/backend/src/app/services/gpio_service.py index a1959e6..fc97264 100644 --- a/backend/src/app/services/gpio_service.py +++ b/backend/src/app/services/gpio_service.py @@ -1,16 +1,25 @@ -try: - # checks if you have access to RPi.GPIO, which is available inside Raspberry PI - import RPi.GPIO as GPIO -except: - # In case of exception, you are executing your script outside of Raspberry PI, so import Mock.GPIO +import logging +from app.config import Config + +logger = logging.getLogger(__name__) + +if Config.TARGET == 'dev': + logger.info("Development environment detected, using mocked GPIO.") from mocks import GPIO as GPIO +else: + try: + import gpiod as GPIO + except ImportError as e: + logger.error("Failed to import RPi.GPIO: %s", e) + raise + -LED_PIN = 10 -GPIO.setmode(GPIO.BCM) -GPIO.setup(LED_PIN, GPIO.OUT) +""" LED_PIN = 10 +GPIO.setmode(GPIO.BCM) +GPIO.setup(LED_PIN, GPIO.OUT) """ def turn_led_on(): - GPIO.output(LED_PIN, GPIO.HIGH) + """ GPIO.output(LED_PIN, GPIO.HIGH)""" def turn_led_off(): - GPIO.output(LED_PIN, GPIO.LOW) \ No newline at end of file + """ GPIO.output(LED_PIN, GPIO.LOW) """ \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 9ca1ae9..e225b36 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,16 +3,48 @@ services: build: context: ./backend dockerfile: Dockerfile + args: + TARGETENV: ${TARGET:-dev} ports: - "${BACKEND_PORT}:${BACKEND_PORT}" environment: - - LOG_LEVEL=Info + - LOG_LEVEL=${LOG_LEVEL} volumes: - ./backend:/app - restart: - always env_file: - .env + profiles: + - non-raspberry-pi + networks: + - memory_network + + backend_rpi: + build: + context: ./backend + dockerfile: Dockerfile + args: + TARGETENV: ${TARGET:-prod} + ports: + - "${BACKEND_PORT}:${BACKEND_PORT}" + environment: + - LOG_LEVEL=${LOG_LEVEL} + volumes: + - ./backend:/app + env_file: + - .env + devices: + - /dev/gpiochip0:/dev/gpiochip0 + - /dev/video0:/dev/video0 + profiles: + - raspberry-pi + networks: + - memory_network + cap_add: + - SYS_ADMIN + - SYS_RAWIO + privileged: true + + frontend: build: @@ -27,12 +59,14 @@ services: - ./frontend:/app - /app/node_modules environment: - - NODE_ENV=development - - CHOKIDAR_USEPOLLING=true - - WATCHPACK_POLLING=true + - NODE_ENV=${NODE_ENV} + - CHOKIDAR_USEPOLLING=${CHOKIDAR_USEPOLLING} + - WATCHPACK_POLLING=${WATCHPACK_POLLING} env_file: - .env - depends_on: - - backend - restart: - always \ No newline at end of file + networks: + - memory_network + +networks: + memory_network: + driver: bridge