From bf0f6e490a0a047854808834d18c5a9170f91b7f Mon Sep 17 00:00:00 2001 From: Norbert Balaz Date: Sun, 15 Dec 2024 15:30:45 +0100 Subject: [PATCH 1/7] refactor: improves importing mock or gpio if onn rpi --- backend/src/app/services/gpio_service.py | 19 +++++++++++++------ docker-compose.yml | 6 ++---- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/backend/src/app/services/gpio_service.py b/backend/src/app/services/gpio_service.py index a1959e6..0638b9b 100644 --- a/backend/src/app/services/gpio_service.py +++ b/backend/src/app/services/gpio_service.py @@ -1,12 +1,19 @@ -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 os +from . import logger + +if os.getenv("MOCK_ENV", "false").lower() == "true": + logger.info("Mock environment detected, using mocked GPIO.") from mocks import GPIO as GPIO +else: + try: + import RPi.GPIO as GPIO + except ImportError as e: + logger.error("Failed to import RPi.GPIO: %s", e) + raise + LED_PIN = 10 -GPIO.setmode(GPIO.BCM) +GPIO.setmode(GPIO.BCM) GPIO.setup(LED_PIN, GPIO.OUT) def turn_led_on(): diff --git a/docker-compose.yml b/docker-compose.yml index 9ca1ae9..6caf512 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,14 +3,14 @@ services: build: context: ./backend dockerfile: Dockerfile + args: + TARGETENV: ${TARGETENV:-dev} ports: - "${BACKEND_PORT}:${BACKEND_PORT}" environment: - LOG_LEVEL=Info volumes: - ./backend:/app - restart: - always env_file: - .env @@ -34,5 +34,3 @@ services: - .env depends_on: - backend - restart: - always \ No newline at end of file From 5662a49132eae0c63c32e0d4d778ba413802b23c Mon Sep 17 00:00:00 2001 From: Norbert Balaz Date: Sun, 15 Dec 2024 15:46:09 +0100 Subject: [PATCH 2/7] feat: improve mock condition --- .env.example | 3 ++- backend/src/app/config.py | 7 ++++--- backend/src/app/services/gpio_service.py | 8 +++++--- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/.env.example b/.env.example index 47cb514..943096f 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 +MOCK=true \ No newline at end of file diff --git a/backend/src/app/config.py b/backend/src/app/config.py index c7a9a91..c7857e5 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() + MOCK = os.getenv('MOCK', 'False').upper() == 'TRUE' diff --git a/backend/src/app/services/gpio_service.py b/backend/src/app/services/gpio_service.py index 0638b9b..8a6ed5b 100644 --- a/backend/src/app/services/gpio_service.py +++ b/backend/src/app/services/gpio_service.py @@ -1,7 +1,9 @@ -import os -from . import logger +import logging +from app.config import Config -if os.getenv("MOCK_ENV", "false").lower() == "true": +logger = logging.getLogger(__name__) + +if Config.MOCK: logger.info("Mock environment detected, using mocked GPIO.") from mocks import GPIO as GPIO else: From cadf0d5a211c8ef27927735e03f5573a2021f30b Mon Sep 17 00:00:00 2001 From: Norbert Balaz Date: Sun, 15 Dec 2024 21:02:23 +0100 Subject: [PATCH 3/7] build(backend): add profiles to docker compose --- backend/Dockerfile | 11 ++++++- backend/requirements.dev.txt | 3 ++ ...requirements.txt => requirements.prod.txt} | 0 docker-compose.yml | 31 ++++++++++++++++--- 4 files changed, 40 insertions(+), 5 deletions(-) create mode 100644 backend/requirements.dev.txt rename backend/{requirements.txt => requirements.prod.txt} (100%) diff --git a/backend/Dockerfile b/backend/Dockerfile index 0981ed5..c78217f 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,16 @@ 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 \ + python3-rpi.gpio \ + libcamera-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..8b9ff90 --- /dev/null +++ b/backend/requirements.dev.txt @@ -0,0 +1,3 @@ +flask +flask-cors +python-dotenv \ No newline at end of file diff --git a/backend/requirements.txt b/backend/requirements.prod.txt similarity index 100% rename from backend/requirements.txt rename to backend/requirements.prod.txt diff --git a/docker-compose.yml b/docker-compose.yml index 6caf512..1ccb3f1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,11 +8,34 @@ services: ports: - "${BACKEND_PORT}:${BACKEND_PORT}" environment: - - LOG_LEVEL=Info + - LOG_LEVEL=${LOG_LEVEL} volumes: - ./backend:/app env_file: - .env + profiles: + - non-raspberry-pi + + backend_rpi: + build: + context: ./backend + dockerfile: Dockerfile + args: + TARGETENV: ${TARGETENV:-prod} + ports: + - "${BACKEND_PORT}:${BACKEND_PORT}" + environment: + - LOG_LEVEL=${LOG_LEVEL} + volumes: + - ./backend:/app + env_file: + - .env + devices: + - /dev/gpiomen:/dev/gpiomen + - /dev/video0:/dev/video0 + profiles: + - raspberry-pi + frontend: build: @@ -27,9 +50,9 @@ 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: From fff7af3e5e39f9f821e41f57a36ddc3653d192c8 Mon Sep 17 00:00:00 2001 From: Norbert Balaz Date: Sun, 15 Dec 2024 23:22:10 +0100 Subject: [PATCH 4/7] feat(backend): add differentation between raspberry-pi and non-raspberry-pi + add gpiod dependecy --- .env.example | 2 +- backend/Dockerfile | 5 +++-- backend/requirements.prod.txt | 2 +- backend/src/app/config.py | 2 +- backend/src/app/services/gpio_service.py | 14 +++++++------- docker-compose.yml | 19 ++++++++++++++----- 6 files changed, 27 insertions(+), 17 deletions(-) diff --git a/.env.example b/.env.example index 943096f..00bbb29 100644 --- a/.env.example +++ b/.env.example @@ -11,4 +11,4 @@ BACKEND_PORT=5000 HOST='0.0.0.0' USE_RELOADER=True LOG_LEVEL=Info -MOCK=true \ No newline at end of file +TARGET=dev \ No newline at end of file diff --git a/backend/Dockerfile b/backend/Dockerfile index c78217f..ad4119e 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -12,14 +12,15 @@ RUN apt-get update && apt-get install -y \ RUN if [ "${TARGETENV}" = "prod" ]; then \ apt-get update && apt-get install -y \ - python3-rpi.gpio \ + libgpiod2 \ + libgpiod-dev \ libcamera-dev \ && rm -rf /var/lib/apt/lists/*; \ fi # Upgrade pip and install dependencies RUN pip install --upgrade pip -COPY requirements.${TARGETENV}}.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.prod.txt b/backend/requirements.prod.txt index b8005da..b5c0dfb 100644 --- a/backend/requirements.prod.txt +++ b/backend/requirements.prod.txt @@ -1,4 +1,4 @@ flask flask-cors -RPi.GPIO +gpiod python-dotenv \ No newline at end of file diff --git a/backend/src/app/config.py b/backend/src/app/config.py index c7857e5..a425520 100644 --- a/backend/src/app/config.py +++ b/backend/src/app/config.py @@ -9,4 +9,4 @@ class Config: PORT = os.getenv('BACKEND_PORT', 5000) USE_RELOADER = os.getenv('USE_RELOADER').upper() == 'TRUE' LOG_LEVEL = os.getenv('LOG_LEVEL', 'ERROR').upper() - MOCK = os.getenv('MOCK', 'False').upper() == 'TRUE' + TARGET = os.getenv('TARGET', 'dev') diff --git a/backend/src/app/services/gpio_service.py b/backend/src/app/services/gpio_service.py index 8a6ed5b..fc97264 100644 --- a/backend/src/app/services/gpio_service.py +++ b/backend/src/app/services/gpio_service.py @@ -3,23 +3,23 @@ logger = logging.getLogger(__name__) -if Config.MOCK: - logger.info("Mock environment detected, using mocked GPIO.") +if Config.TARGET == 'dev': + logger.info("Development environment detected, using mocked GPIO.") from mocks import GPIO as GPIO else: try: - import RPi.GPIO as GPIO + import gpiod as GPIO except ImportError as e: logger.error("Failed to import RPi.GPIO: %s", e) raise -LED_PIN = 10 +""" LED_PIN = 10 GPIO.setmode(GPIO.BCM) -GPIO.setup(LED_PIN, GPIO.OUT) +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 1ccb3f1..8bcfdaa 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,7 +4,7 @@ services: context: ./backend dockerfile: Dockerfile args: - TARGETENV: ${TARGETENV:-dev} + TARGETENV: ${TARGET:-dev} ports: - "${BACKEND_PORT}:${BACKEND_PORT}" environment: @@ -15,13 +15,15 @@ services: - .env profiles: - non-raspberry-pi + networks: + - memory_network backend_rpi: build: context: ./backend dockerfile: Dockerfile args: - TARGETENV: ${TARGETENV:-prod} + TARGETENV: ${TARGET:-prod} ports: - "${BACKEND_PORT}:${BACKEND_PORT}" environment: @@ -31,10 +33,13 @@ services: env_file: - .env devices: - - /dev/gpiomen:/dev/gpiomen + - /dev/gpiochip0:/dev/gpiochip0 - /dev/video0:/dev/video0 profiles: - raspberry-pi + networks: + - memory_network + frontend: @@ -55,5 +60,9 @@ services: - WATCHPACK_POLLING=${WATCHPACK_POLLING} env_file: - .env - depends_on: - - backend + networks: + - memory_network + +networks: + memory_network: + driver: bridge From fc2e8dd3d143b1290563ad56831e42ad201337be Mon Sep 17 00:00:00 2001 From: Norbert Balaz Date: Mon, 16 Dec 2024 00:33:55 +0100 Subject: [PATCH 5/7] feat(backend): add opencv and camera module 3 basic setup with errors --- backend/Dockerfile | 4 +++- backend/requirements.prod.txt | 5 ++++- backend/src/app/__init__.py | 8 ++++++++ backend/src/app/routes.py | 14 ++++++++++++-- backend/src/app/services/camera_service.py | 20 ++++++++++++++++++++ docker-compose.yml | 2 ++ 6 files changed, 49 insertions(+), 4 deletions(-) create mode 100644 backend/src/app/services/camera_service.py diff --git a/backend/Dockerfile b/backend/Dockerfile index ad4119e..d26a8ea 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -11,7 +11,9 @@ RUN apt-get update && apt-get install -y \ && rm -rf /var/lib/apt/lists/* RUN if [ "${TARGETENV}" = "prod" ]; then \ - apt-get update && apt-get install -y \ + apt-get update && apt-get install -y \ + cmake \ + python3-opencv \ libgpiod2 \ libgpiod-dev \ libcamera-dev \ diff --git a/backend/requirements.prod.txt b/backend/requirements.prod.txt index b5c0dfb..9f47cb3 100644 --- a/backend/requirements.prod.txt +++ b/backend/requirements.prod.txt @@ -1,4 +1,7 @@ flask flask-cors gpiod -python-dotenv \ No newline at end of file +python-dotenv +opencv-python +dlib +face-recognition \ No newline at end of file diff --git a/backend/src/app/__init__.py b/backend/src/app/__init__.py index 18ff085..d34faf7 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,12 @@ def create_app(): from app.routes import main_routes app.register_blueprint(main_routes) + camera_service = CameraService() + + @app.teardown_appcontext + def cleanup(exception=None): + camera_service.release_camera() + return app def config_logger(app, log_Level='ERROR'): @@ -31,3 +38,4 @@ def config_logger(app, log_Level='ERROR'): console_handler.setFormatter(formatter) app.logger.addHandler(console_handler) + diff --git a/backend/src/app/routes.py b/backend/src/app/routes.py index 5d2e2c3..c602f0d 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..236e57c --- /dev/null +++ b/backend/src/app/services/camera_service.py @@ -0,0 +1,20 @@ +import cv2 + +class CameraService: + def __init__(self): + # Initialize the camera (default device index is 0) + self.cap = cv2.VideoCapture(0) + 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) + + def capture_frame(self): + # Capture a frame from the camera + ret, frame = self.cap.read() + if not ret: + raise RuntimeError("Failed to capture frame from camera") + return frame + + def release_camera(self): + # Release the camera resource + self.cap.release() diff --git a/docker-compose.yml b/docker-compose.yml index 8bcfdaa..66da3b8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -39,6 +39,8 @@ services: - raspberry-pi networks: - memory_network + cap_add: + - SYS_ADMIN From c873a8a49d0cdc9f66f05fc68830184d194e8cf9 Mon Sep 17 00:00:00 2001 From: Norbert Balaz Date: Mon, 16 Dec 2024 13:40:07 +0100 Subject: [PATCH 6/7] fix(backend): add opencv to non-raspberry pi --- backend/requirements.dev.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/requirements.dev.txt b/backend/requirements.dev.txt index 8b9ff90..6cf5a3a 100644 --- a/backend/requirements.dev.txt +++ b/backend/requirements.dev.txt @@ -1,3 +1,4 @@ flask flask-cors -python-dotenv \ No newline at end of file +python-dotenv +opencv-python \ No newline at end of file From 4095d9394e5a70b3167dcbbecb00dc4e05339ea3 Mon Sep 17 00:00:00 2001 From: Norbert Balaz Date: Mon, 16 Dec 2024 14:22:25 +0100 Subject: [PATCH 7/7] EIN TOLLES FEATURE --- backend/Dockerfile | 7 ++++++ backend/src/app/__init__.py | 7 +++--- backend/src/app/routes.py | 4 ++-- backend/src/app/services/camera_service.py | 26 ++++++++++++++++++---- docker-compose.yml | 2 ++ 5 files changed, 37 insertions(+), 9 deletions(-) diff --git a/backend/Dockerfile b/backend/Dockerfile index d26a8ea..c9feec1 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -17,6 +17,13 @@ RUN if [ "${TARGETENV}" = "prod" ]; then \ 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 diff --git a/backend/src/app/__init__.py b/backend/src/app/__init__.py index d34faf7..e4ddb14 100644 --- a/backend/src/app/__init__.py +++ b/backend/src/app/__init__.py @@ -19,12 +19,13 @@ def create_app(): from app.routes import main_routes app.register_blueprint(main_routes) - camera_service = CameraService() + """ camera_service = CameraService() + camera_service.test_camera_feed() """ - @app.teardown_appcontext + """ @app.teardown_appcontext def cleanup(exception=None): camera_service.release_camera() - + """ return app def config_logger(app, log_Level='ERROR'): diff --git a/backend/src/app/routes.py b/backend/src/app/routes.py index c602f0d..02007e6 100644 --- a/backend/src/app/routes.py +++ b/backend/src/app/routes.py @@ -36,7 +36,7 @@ def led_off(): except Exception as e: return create_response(f"Error: {str(e)}", "error", 500) -@main_routes.route('/capture', methods=['GET']) +""" @main_routes.route('/capture', methods=['GET']) def capture_image(): try: frame = camera_service.capture_frame() @@ -44,4 +44,4 @@ def capture_image(): _, 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 + 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 index 236e57c..fe919fe 100644 --- a/backend/src/app/services/camera_service.py +++ b/backend/src/app/services/camera_service.py @@ -2,19 +2,37 @@ class CameraService: def __init__(self): - # Initialize the camera (default device index is 0) - self.cap = cv2.VideoCapture(0) + 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): - # Capture a frame from the camera ret, frame = self.cap.read() if not ret: raise RuntimeError("Failed to capture frame from camera") return frame def release_camera(self): - # Release the camera resource 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/docker-compose.yml b/docker-compose.yml index 66da3b8..e225b36 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -41,6 +41,8 @@ services: - memory_network cap_add: - SYS_ADMIN + - SYS_RAWIO + privileged: true