From 7c5ab985533dfcdc7da9b2ca1c29387bbf715859 Mon Sep 17 00:00:00 2001 From: Nicolas Bertrand Date: Fri, 10 Oct 2025 00:42:36 +0200 Subject: [PATCH] Working --- src/nenuscanner/__init__.py | 2 ++ src/nenuscanner/camera.py | 34 ++++++++++++++++++--------- src/nenuscanner/leds.py | 14 +++++++---- src/nenuscanner/routes/__init__.py | 16 +++++++++++++ src/nenuscanner/routes/camera.py | 16 ++++++------- src/nenuscanner/routes/leds.py | 33 ++------------------------ src/nenuscanner/templates/base.html | 5 ++++ src/nenuscanner/templates/camera.html | 21 +++++++++++------ 8 files changed, 79 insertions(+), 62 deletions(-) diff --git a/src/nenuscanner/__init__.py b/src/nenuscanner/__init__.py index e3b7d14..9759684 100644 --- a/src/nenuscanner/__init__.py +++ b/src/nenuscanner/__init__.py @@ -2,6 +2,8 @@ from flask import Flask, send_from_directory, session import os from . import db, config, routes, utils, leds +leds.get().enter() + app = Flask(__name__) # Manage secret key diff --git a/src/nenuscanner/camera.py b/src/nenuscanner/camera.py index de9f333..8c443f4 100644 --- a/src/nenuscanner/camera.py +++ b/src/nenuscanner/camera.py @@ -6,6 +6,8 @@ import shutil from . import leds, config import subprocess import json +from PIL import Image +import io def parse_config(lines): @@ -62,14 +64,18 @@ class Camera: class RealCamera(Camera): def __init__(self): + self._entered = False self.inner = gp.Camera() def __enter__(self): - self.inner.init() + if not self._entered: + self._entered = True + self.inner.init() return self def __exit__(self, *args): - self.inner.exit() + # self.inner.exit() + pass def capture(self): try: @@ -79,14 +85,11 @@ class RealCamera(Camera): return None def capture_preview(self): - try: - subprocess.run( - "gphoto2 --capture-preview --stdout > src/nenuscanner/static/feed.jpg", - shell=True, check=True - ) - except subprocess.CalledProcessError as e: - print('An error occured when capturing photo', e) - raise CameraException(f"Erreur lors de la capture de l'image: {e}") + capture = gp.check_result(gp.gp_camera_capture_preview(self.inner)) + file_data = gp.check_result(gp.gp_file_get_data_and_size(capture)) + data = memoryview(file_data) + image = Image.open(io.BytesIO(file_data)) + image.save("src/nenuscanner/static/feed.jpg") def save(self, capture, output_file): preview = self.inner.file_get(capture.folder, capture.name[:-3] + 'JPG', gp.GP_FILE_TYPE_NORMAL) @@ -97,8 +100,17 @@ class RealCamera(Camera): raw.save(output_file + '.cr2') def config(self): + + was_entered = self._entered + if self._entered: + self._entered = False + self.inner.exit() + res = subprocess.run(["gphoto2", "--list-all-config"], capture_output=True, encoding="utf-8") + if was_entered: + self.__enter__() + # print(res.stdout[:200]) configs = res.stdout.split("\n") @@ -162,4 +174,4 @@ def set_config(parameter, value): class CameraException(Exception): """Exception personnalisée pour les erreurs liées à la caméra.""" def __init__(self, message): - super().__init__(message) \ No newline at end of file + super().__init__(message) diff --git a/src/nenuscanner/leds.py b/src/nenuscanner/leds.py index aa2c15c..47a8117 100644 --- a/src/nenuscanner/leds.py +++ b/src/nenuscanner/leds.py @@ -46,19 +46,25 @@ class GpioLed: class GpioLeds(Leds): def __init__(self, chip: str, gpio_pins: list[int]): + self._entered = False self.chip = gpiod.Chip(chip) self.leds = [] + for pin in gpio_pins: self.leds.append(GpioLed(pin)) def __enter__(self): - for led in self.leds: - led.enter(self.chip) + if not self._entered: + self._entered = True + for led in self.leds: + led.enter(self.chip) return self def __exit__(self, *args): - for led in self.leds: - led.exit() + # for led in self.leds: + # led.exit() + # self._entered = False + pass def off(self): for led in self.leds: diff --git a/src/nenuscanner/routes/__init__.py b/src/nenuscanner/routes/__init__.py index 9f9fafc..b9fe7e2 100644 --- a/src/nenuscanner/routes/__init__.py +++ b/src/nenuscanner/routes/__init__.py @@ -1,3 +1,4 @@ +import subprocess from flask import Blueprint, render_template from .. import db @@ -16,6 +17,21 @@ def index(): projects = db.Object.all_by_project(conn) return render_template('index.html', projects=projects) +# Route that restarts the server +@blueprint.route("/restart") +def restart(): + """ + Serves the index of nenuscanner. + """ + subprocess.Popen( + ["bash", "-c", "sleep 1 && systemctl restart nenuscanner --user"], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + start_new_session=True + ) + return render_template('restart.html') + + blueprint.register_blueprint(object.blueprint, url_prefix='/object') blueprint.register_blueprint(calibration.blueprint, url_prefix='/calibration') diff --git a/src/nenuscanner/routes/camera.py b/src/nenuscanner/routes/camera.py index 6b49dde..8c78812 100644 --- a/src/nenuscanner/routes/camera.py +++ b/src/nenuscanner/routes/camera.py @@ -44,13 +44,11 @@ def set_camera_settings(): return {'status': 'ok', **updated} -@blueprint.route('/feed.jpg') +@blueprint.route('/feed.jpg', methods=['GET']) def camera_feed(): + capture_preview() return send_file('static/feed.jpg', mimetype='image/jpeg') - - - @blueprint.route('/config', methods=['GET']) def get_camera_config(): """ @@ -96,14 +94,14 @@ def get_camera_config(): return jsonify(grouped_params) -@blueprint.route('/capture_preview', methods=['POST']) +# @blueprint.route('/capture_preview', methods=['POST']) def capture_preview(): """ Capture un aperçu avec gphoto2 et sauvegarde dans static/feed.jpg """ try: - cam = C.get() - cam.capture_preview() - return jsonify({'status': 'ok'}) + with C.get() as cam: + cam.capture_preview() + return jsonify({'status': 'ok'}) except C.CameraException as e: - return jsonify({'status': 'error', 'error': str(e)}), 500 \ No newline at end of file + return jsonify({'status': 'error', 'error': str(e)}), 500 diff --git a/src/nenuscanner/routes/leds.py b/src/nenuscanner/routes/leds.py index 6226114..d0806e6 100644 --- a/src/nenuscanner/routes/leds.py +++ b/src/nenuscanner/routes/leds.py @@ -9,36 +9,6 @@ from .. import leds,config blueprint = Blueprint('leds', __name__) -def _get_gpio_leds(): - """Return a singleton leds controller stored in app.extensions.""" - app = current_app._get_current_object() - ext_key = 'nenuscanner_gpio_leds' - if ext_key not in app.extensions: - # create and store the resource - app.extensions[ext_key] = leds.get().enter() - return app.extensions[ext_key] - -# WARNING : how to release - -#@blueprint.record_once -#def _register_cleanup(state): -# """ -#Register a teardown handler on the app that will try to exit the leds controller -# when the app context is torn down. -# """ -# app = state.app -# ext_key = 'nenuscanner_gpio_leds' -# @app.teardown_appcontext -# def _cleanup(exception=None): -# gpio = app.extensions.get(ext_key) -# if gpio: -# try: -# gpio.exit() -# except Exception: -# # best effort cleanup, don't raise during teardown -# pass - - # Routes for object management @blueprint.route('/') @@ -61,10 +31,11 @@ def set_led(): led = data.get('led') state = data.get('state') # get the controller (lazy, stored on app.extensions) - gpio_leds = _get_gpio_leds() + gpio_leds = leds.get() try: # parse led id/name according to your naming convention + print([x.gpio_pin for x in gpio_leds.leds]) gpio_led = gpio_leds.get_by_uuid(int(led)) print(f"Setting {led} / {gpio_led} to {state}") if state == "on": diff --git a/src/nenuscanner/templates/base.html b/src/nenuscanner/templates/base.html index 65bb828..136fc4e 100644 --- a/src/nenuscanner/templates/base.html +++ b/src/nenuscanner/templates/base.html @@ -23,7 +23,12 @@