Compare commits
No commits in common. "13c9bdfd2536aef97cfead4202d95233970366bf" and "8d1dff34701b97d3bdfcb22a3e6b2f8a7f1d8b82" have entirely different histories.
13c9bdfd25
...
8d1dff3470
|
|
@ -184,9 +184,3 @@ poetry.toml
|
||||||
pyrightconfig.json
|
pyrightconfig.json
|
||||||
|
|
||||||
# End of https://www.toptal.com/developers/gitignore/api/python
|
# End of https://www.toptal.com/developers/gitignore/api/python
|
||||||
|
|
||||||
|
|
||||||
# Custom ignores
|
|
||||||
src/nenuscanner/static/feed.jpg
|
|
||||||
db.sqlite
|
|
||||||
|
|
||||||
|
|
|
||||||
42
Readme.md
42
Readme.md
|
|
@ -1,41 +1,5 @@
|
||||||
# NenuScanner
|
# NenuScanner
|
||||||
|
|
||||||
## Lancer l application flask depuis la racine
|
|
||||||
|
|
||||||
|
|
||||||
### Intall local de l applcation
|
|
||||||
|
|
||||||
Avoir un venv
|
|
||||||
|
|
||||||
puis
|
|
||||||
|
|
||||||
```
|
|
||||||
pip install -e .
|
|
||||||
```
|
|
||||||
|
|
||||||
dans src/nenuscanner
|
|
||||||
|
|
||||||
cp config.local.py dans config.py
|
|
||||||
|
|
||||||
|
|
||||||
### Lancer l'application
|
|
||||||
|
|
||||||
```
|
|
||||||
flask --app . run --debug
|
|
||||||
```
|
|
||||||
|
|
||||||
resoudre dependance si besoins genre:
|
|
||||||
|
|
||||||
```
|
|
||||||
pip install gpiod==2.1.3
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## GPIO
|
## GPIO
|
||||||
|
|
||||||
|
|
@ -119,12 +83,6 @@ gphoto2 --set-config iso=1
|
||||||
|
|
||||||
## Hardware
|
## Hardware
|
||||||
|
|
||||||
### LED
|
|
||||||
|
|
||||||
LED COB, Cree LED, série CXA2, 90CRI, 2700K Blanc, CXB1512-0000-000N0UK427G 22W
|
|
||||||
|
|
||||||
https://fr.rs-online.com/web/p/led-cob/8847355
|
|
||||||
|
|
||||||
### Connecteur
|
### Connecteur
|
||||||
|
|
||||||
VsAT ID
|
VsAT ID
|
||||||
|
|
|
||||||
|
|
@ -1,51 +0,0 @@
|
||||||
#!/usr/bin/env python
|
|
||||||
|
|
||||||
# python-gphoto2 - Python interface to libgphoto2
|
|
||||||
# http://github.com/jim-easterbrook/python-gphoto2
|
|
||||||
# Copyright (C) 2015-22 Jim Easterbrook jim@jim-easterbrook.me.uk
|
|
||||||
#
|
|
||||||
# This file is part of python-gphoto2.
|
|
||||||
#
|
|
||||||
# python-gphoto2 is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU Lesser General Public License as
|
|
||||||
# published by the Free Software Foundation, either version 3 of the
|
|
||||||
# License, or (at your option) any later version.
|
|
||||||
#
|
|
||||||
# python-gphoto2 is distributed in the hope that it will be useful, but
|
|
||||||
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
||||||
# Lesser General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU Lesser General Public
|
|
||||||
# License along with python-gphoto2. If not, see
|
|
||||||
# <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
import logging
|
|
||||||
import locale
|
|
||||||
import os
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
|
|
||||||
import gphoto2 as gp
|
|
||||||
|
|
||||||
def main():
|
|
||||||
locale.setlocale(locale.LC_ALL, '')
|
|
||||||
logging.basicConfig(
|
|
||||||
format='%(levelname)s: %(name)s: %(message)s', level=logging.WARNING)
|
|
||||||
callback_obj = gp.check_result(gp.use_python_logging())
|
|
||||||
camera = gp.Camera()
|
|
||||||
camera.init()
|
|
||||||
print('Capturing image')
|
|
||||||
file_path = camera.capture(gp.GP_CAPTURE_IMAGE)
|
|
||||||
print('Camera file path: {0}/{1}'.format(file_path.folder, file_path.name))
|
|
||||||
target = os.path.join('/tmp', file_path.name)
|
|
||||||
print('Copying image to', target)
|
|
||||||
camera_file = camera.file_get(
|
|
||||||
file_path.folder, file_path.name, gp.GP_FILE_TYPE_NORMAL)
|
|
||||||
camera_file.save(target)
|
|
||||||
subprocess.call(['xdg-open', target])
|
|
||||||
camera.exit()
|
|
||||||
return 0
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
sys.exit(main())
|
|
||||||
|
|
@ -1,64 +1,13 @@
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
from flask import jsonify
|
|
||||||
import gphoto2 as gp
|
import gphoto2 as gp
|
||||||
import shutil
|
import shutil
|
||||||
from . import leds, config
|
from . import leds, config
|
||||||
import subprocess
|
|
||||||
import json
|
|
||||||
|
|
||||||
|
|
||||||
def parse_config(lines):
|
|
||||||
config = {}
|
|
||||||
block = []
|
|
||||||
for line in lines:
|
|
||||||
line = line.strip()
|
|
||||||
if not line:
|
|
||||||
continue
|
|
||||||
if line.startswith('/main/'):
|
|
||||||
# Nouveau bloc : traiter le précédent s'il existe
|
|
||||||
if block:
|
|
||||||
insert_block(config, block)
|
|
||||||
block = []
|
|
||||||
block.append(line)
|
|
||||||
elif line == 'END':
|
|
||||||
block.append(line)
|
|
||||||
insert_block(config, block)
|
|
||||||
block = []
|
|
||||||
else:
|
|
||||||
block.append(line)
|
|
||||||
return config
|
|
||||||
|
|
||||||
|
|
||||||
def insert_block(config, block):
|
|
||||||
path = block[0].strip('/').split('/') # ['main', 'actions', 'syncdatetimeutc']
|
|
||||||
data = {}
|
|
||||||
for line in block[1:-1]: # Exclure le chemin et 'END'
|
|
||||||
if ':' in line:
|
|
||||||
key, value = line.split(':', 1)
|
|
||||||
key = key.strip()
|
|
||||||
value = value.strip()
|
|
||||||
if key.startswith('Choice'):
|
|
||||||
# Extrait les choix dans une liste
|
|
||||||
idx, label = value.split(' ', 1)
|
|
||||||
data.setdefault('Choices', []).append({'id': int(idx), 'label': label})
|
|
||||||
else:
|
|
||||||
data[key] = value
|
|
||||||
|
|
||||||
# Insère dans le dictionnaire hiérarchique
|
|
||||||
d = config
|
|
||||||
for part in path[:-1]: # Traverse les sections, ex: main -> actions
|
|
||||||
d = d.setdefault(part, {})
|
|
||||||
d[path[-1]] = data # Attribue les données à la clé finale
|
|
||||||
|
|
||||||
|
|
||||||
class Camera:
|
class Camera:
|
||||||
def capture(self):
|
def capture(self):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def config(self):
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
class RealCamera(Camera):
|
class RealCamera(Camera):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
@ -78,16 +27,6 @@ class RealCamera(Camera):
|
||||||
print('An error occured when capturing photo', e)
|
print('An error occured when capturing photo', e)
|
||||||
return None
|
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}")
|
|
||||||
|
|
||||||
def save(self, capture, output_file):
|
def save(self, capture, output_file):
|
||||||
preview = self.inner.file_get(capture.folder, capture.name[:-3] + 'JPG', gp.GP_FILE_TYPE_NORMAL)
|
preview = self.inner.file_get(capture.folder, capture.name[:-3] + 'JPG', gp.GP_FILE_TYPE_NORMAL)
|
||||||
raw = self.inner.file_get(capture.folder, capture.name, gp.GP_FILE_TYPE_RAW)
|
raw = self.inner.file_get(capture.folder, capture.name, gp.GP_FILE_TYPE_RAW)
|
||||||
|
|
@ -96,23 +35,6 @@ class RealCamera(Camera):
|
||||||
subprocess.run(['convert', output_file + '.jpg', '-resize', '10%', output_file + '.jpg'])
|
subprocess.run(['convert', output_file + '.jpg', '-resize', '10%', output_file + '.jpg'])
|
||||||
raw.save(output_file + '.cr2')
|
raw.save(output_file + '.cr2')
|
||||||
|
|
||||||
def config(self):
|
|
||||||
res = subprocess.run(["gphoto2", "--list-all-config"], capture_output=True, encoding="utf-8")
|
|
||||||
|
|
||||||
# print(res.stdout[:200])
|
|
||||||
|
|
||||||
configs = res.stdout.split("\n")
|
|
||||||
print(configs)
|
|
||||||
config_dict = parse_config(configs)
|
|
||||||
|
|
||||||
# Sauvegarde en JSON
|
|
||||||
with open("configCamera.json", "w", encoding="utf-8") as f:
|
|
||||||
json.dump(config_dict, f, indent=2, ensure_ascii=False)
|
|
||||||
|
|
||||||
def set_config(self, parameter, value):
|
|
||||||
subprocess.run(["gphoto2", "--set-config", f"{parameter}={value}"])
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
class DummyCamera(Camera):
|
class DummyCamera(Camera):
|
||||||
def __init__(self, leds: leds.DummyLeds):
|
def __init__(self, leds: leds.DummyLeds):
|
||||||
|
|
@ -151,15 +73,3 @@ camera = DummyCamera(leds.get()) if config.CAMERA == "dummy" else RealCamera()
|
||||||
|
|
||||||
def get():
|
def get():
|
||||||
return camera
|
return camera
|
||||||
|
|
||||||
|
|
||||||
def config():
|
|
||||||
return camera.config()
|
|
||||||
|
|
||||||
def set_config(parameter, value):
|
|
||||||
return camera.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)
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
from flask import Blueprint, render_template
|
from flask import Blueprint, render_template
|
||||||
|
|
||||||
from .. import db
|
from .. import db
|
||||||
from . import object, calibration, acquisition, camera, leds
|
from . import object, calibration, acquisition
|
||||||
|
|
||||||
blueprint = Blueprint('routes', __name__)
|
blueprint = Blueprint('routes', __name__)
|
||||||
|
|
||||||
|
|
@ -20,5 +20,3 @@ def index():
|
||||||
blueprint.register_blueprint(object.blueprint, url_prefix='/object')
|
blueprint.register_blueprint(object.blueprint, url_prefix='/object')
|
||||||
blueprint.register_blueprint(calibration.blueprint, url_prefix='/calibration')
|
blueprint.register_blueprint(calibration.blueprint, url_prefix='/calibration')
|
||||||
blueprint.register_blueprint(acquisition.blueprint, url_prefix='/acquisition')
|
blueprint.register_blueprint(acquisition.blueprint, url_prefix='/acquisition')
|
||||||
blueprint.register_blueprint(camera.blueprint, url_prefix='/camera')
|
|
||||||
blueprint.register_blueprint(leds.blueprint, url_prefix='/leds')
|
|
||||||
|
|
|
||||||
|
|
@ -1,109 +0,0 @@
|
||||||
from flask import Blueprint, render_template, request, send_file, jsonify
|
|
||||||
import json
|
|
||||||
import subprocess
|
|
||||||
|
|
||||||
from .. import camera as C
|
|
||||||
|
|
||||||
|
|
||||||
blueprint = Blueprint('camera', __name__)
|
|
||||||
|
|
||||||
# Routes for object management
|
|
||||||
|
|
||||||
|
|
||||||
@blueprint.route('/')
|
|
||||||
def get():
|
|
||||||
"""
|
|
||||||
Returns the page showing camera configuration for all parameters in capturesettings and imgsettings,
|
|
||||||
grouped by section.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return render_template(
|
|
||||||
'camera.html')
|
|
||||||
|
|
||||||
|
|
||||||
@blueprint.route('/set', methods=['POST'])
|
|
||||||
def set_camera_settings():
|
|
||||||
"""
|
|
||||||
Receives and processes new camera settings for all parameters from the client.
|
|
||||||
"""
|
|
||||||
data = request.get_json()
|
|
||||||
updated = {}
|
|
||||||
for key, value in data.items():
|
|
||||||
print(f"Received {key}: {value}")
|
|
||||||
C.set_config(key, value)
|
|
||||||
updated[key] = value
|
|
||||||
|
|
||||||
try:
|
|
||||||
cam = C.get()
|
|
||||||
cam.capture_preview()
|
|
||||||
return jsonify({'status': 'ok'})
|
|
||||||
except C.CameraException as e:
|
|
||||||
return jsonify({'status': 'error', 'error': str(e)}), 500
|
|
||||||
|
|
||||||
return {'status': 'ok', **updated}
|
|
||||||
|
|
||||||
@blueprint.route('/feed.jpg')
|
|
||||||
def camera_feed():
|
|
||||||
return send_file('static/feed.jpg', mimetype='image/jpeg')
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@blueprint.route('/config', methods=['GET'])
|
|
||||||
def get_camera_config():
|
|
||||||
"""
|
|
||||||
Returns grouped camera parameters as JSON for frontend JS.
|
|
||||||
"""
|
|
||||||
|
|
||||||
try:
|
|
||||||
cam = C.get()
|
|
||||||
cam.config()
|
|
||||||
|
|
||||||
except C.CameraException as e:
|
|
||||||
return jsonify({'status': 'error', 'error': str(e)}), 500
|
|
||||||
|
|
||||||
with open('configCamera.json', 'r') as f:
|
|
||||||
config = json.load(f)
|
|
||||||
|
|
||||||
grouped_params = []
|
|
||||||
# Iterate over all sections in config['main']
|
|
||||||
for section, settings in config['main'].items():
|
|
||||||
section_params = []
|
|
||||||
if isinstance(settings, dict):
|
|
||||||
for param_name, param in settings.items():
|
|
||||||
if 'Choices' in param and isinstance(param['Choices'], list) and param['Choices']:
|
|
||||||
choices = [
|
|
||||||
{'value': c.get('id', idx), 'label': c['label']}
|
|
||||||
for idx, c in enumerate(param['Choices'])
|
|
||||||
]
|
|
||||||
|
|
||||||
section_params.append({
|
|
||||||
'name': param_name,
|
|
||||||
'label': param.get('Label', param_name.capitalize()),
|
|
||||||
'choices': choices,
|
|
||||||
'current': param.get('Current', ''),
|
|
||||||
'Type': param.get('Type', 'Text'),
|
|
||||||
'Readonly': param.get('Readonly', 0)
|
|
||||||
})
|
|
||||||
|
|
||||||
if section_params:
|
|
||||||
grouped_params.append({
|
|
||||||
'section': section,
|
|
||||||
'params': section_params
|
|
||||||
})
|
|
||||||
|
|
||||||
return jsonify(grouped_params)
|
|
||||||
|
|
||||||
@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'})
|
|
||||||
except C.CameraException as e:
|
|
||||||
return jsonify({'status': 'error', 'error': str(e)}), 500
|
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
from flask import Blueprint, render_template, request, send_file, jsonify
|
|
||||||
import json
|
|
||||||
import subprocess
|
|
||||||
|
|
||||||
from .. import camera as C
|
|
||||||
from .. import leds
|
|
||||||
|
|
||||||
|
|
||||||
blueprint = Blueprint('leds', __name__)
|
|
||||||
|
|
||||||
# Routes for object management
|
|
||||||
|
|
||||||
|
|
||||||
@blueprint.route('/')
|
|
||||||
def get():
|
|
||||||
"""
|
|
||||||
Returns the pages showing all leds.
|
|
||||||
"""
|
|
||||||
|
|
||||||
return render_template(
|
|
||||||
'leds.html')
|
|
||||||
|
|
||||||
@blueprint.route('/set', methods=['POST'])
|
|
||||||
def set_led():
|
|
||||||
"""
|
|
||||||
Reçoit une commande pour allumer ou éteindre une LED.
|
|
||||||
Attend un JSON : { "led": "led1", "state": "on" } ou { "led": "led2", "state": "off" }
|
|
||||||
"""
|
|
||||||
data = request.get_json()
|
|
||||||
led = data.get('led')
|
|
||||||
state = data.get('state')
|
|
||||||
ledId=int(led[3])
|
|
||||||
|
|
||||||
with leds.get() as gpio_leds:
|
|
||||||
print(gpio_leds.leds)
|
|
||||||
gpio_led=gpio_leds.leds[ledId]
|
|
||||||
if state == "on":
|
|
||||||
gpio_led.on()
|
|
||||||
else:
|
|
||||||
gpio_led.off()
|
|
||||||
|
|
||||||
|
|
||||||
print(f"Commande reçue pour {led} : {state}")
|
|
||||||
|
|
||||||
return jsonify({'status': 'ok', 'led': led, 'state': state})
|
|
||||||
|
|
@ -1,59 +0,0 @@
|
||||||
.cameraSettings {
|
|
||||||
|
|
||||||
height: 800px;
|
|
||||||
border: 1px solid;
|
|
||||||
overflow: scroll;
|
|
||||||
scrollbar-color: red orange;
|
|
||||||
scrollbar-width: thin;
|
|
||||||
}
|
|
||||||
img#preview {
|
|
||||||
width: 640px;
|
|
||||||
height: 480px;
|
|
||||||
border: 1px solid black;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.switch-slice {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5em;
|
|
||||||
}
|
|
||||||
.switch-slice label {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
.switch-slice .switch {
|
|
||||||
position: relative;
|
|
||||||
display: inline-block;
|
|
||||||
width: 50px;
|
|
||||||
height: 24px;
|
|
||||||
}
|
|
||||||
.switch-slice .switch input {
|
|
||||||
opacity: 0;
|
|
||||||
width: 0;
|
|
||||||
height: 0;
|
|
||||||
}
|
|
||||||
.switch-slice .slider {
|
|
||||||
position: absolute;
|
|
||||||
cursor: pointer;
|
|
||||||
top: 0; left: 0; right: 0; bottom: 0;
|
|
||||||
background-color: #ccc;
|
|
||||||
transition: .4s;
|
|
||||||
border-radius: 24px;
|
|
||||||
}
|
|
||||||
.switch-slice .slider:before {
|
|
||||||
position: absolute;
|
|
||||||
content: "";
|
|
||||||
height: 18px;
|
|
||||||
width: 18px;
|
|
||||||
left: 3px;
|
|
||||||
bottom: 3px;
|
|
||||||
background-color: white;
|
|
||||||
transition: .4s;
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
.switch-slice input:checked + .slider {
|
|
||||||
background-color: #48c774;
|
|
||||||
}
|
|
||||||
.switch-slice input:checked + .slider:before {
|
|
||||||
transform: translateX(26px);
|
|
||||||
}
|
|
||||||
|
|
@ -4,9 +4,7 @@
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>NenuScanner</title>
|
<title>NenuScanner</title>
|
||||||
|
|
||||||
<link rel="stylesheet" href="/static/bulma.min.css">
|
<link rel="stylesheet" href="/static/bulma.min.css">
|
||||||
<link rel="stylesheet" href="/static/custom.css">
|
|
||||||
{% block extracss %}{% endblock extracss %}
|
{% block extracss %}{% endblock extracss %}
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
|
||||||
|
|
@ -1,163 +0,0 @@
|
||||||
{% extends "base.html" %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<section class="section">
|
|
||||||
<div class="container">
|
|
||||||
<h1 class="title">Configurer la camera</h1>
|
|
||||||
<form id="camera-config-form">
|
|
||||||
<div class="columns">
|
|
||||||
<div class="column is-half">
|
|
||||||
<div class="cameraSettings" id="camera-config-container"></div>
|
|
||||||
</div>
|
|
||||||
<div class="column is-half has-text-centered">
|
|
||||||
<figure class="image is-4by3" style="max-width: 480px; margin: auto;">
|
|
||||||
<img class="preview" id="camera-preview" src="/static/feed.jpg?{{ range(1000000)|random }}" alt="Camera Preview"
|
|
||||||
style="border: 1px solid #ccc;">
|
|
||||||
</figure>
|
|
||||||
<button class="button is-small is-info mt-2" type="button" onclick="refreshPreview()">Rafraîchir
|
|
||||||
l’aperçu
|
|
||||||
</button>
|
|
||||||
<button class="button is-small is-info mt-2" type="button" onclick="CapturePreview()">Acquistion camera
|
|
||||||
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<nav class="level">
|
|
||||||
<div class="level-item has-text-centered">
|
|
||||||
<div>
|
|
||||||
<p class="heading">Speed</p>
|
|
||||||
<p id="display-camera-shutterspeed" class="title is-5">Updating ...</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="level-item has-text-centered">
|
|
||||||
<div>
|
|
||||||
<p class="heading">f/</p>
|
|
||||||
<p id="display-camera-aperture" class="title is-5"> Updating ...</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="level-item has-text-centered">
|
|
||||||
<div>
|
|
||||||
<p class="heading">ISO</p>
|
|
||||||
<p id="display-camera-iso" class="title is-5">Updating ...</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="level-item has-text-centered">
|
|
||||||
<div>
|
|
||||||
<p class="heading">°K</p>
|
|
||||||
<p id="display-camera-color-temperature" class="title is-5">Updating ...</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
{% endblock content %}
|
|
||||||
|
|
||||||
{% block extrajs %}
|
|
||||||
<script>
|
|
||||||
function setConfig(paramName) {
|
|
||||||
const value = document.getElementById(paramName).value;
|
|
||||||
fetch('/camera/set', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ [paramName]: value })
|
|
||||||
}).then(() => location.reload());
|
|
||||||
}
|
|
||||||
|
|
||||||
window.refreshPreview = function () {
|
|
||||||
const img = document.getElementById('camera-preview');
|
|
||||||
const url = '/static/feed.jpg?' + new Date().getTime();
|
|
||||||
img.src = url;
|
|
||||||
}
|
|
||||||
setInterval(window.refreshPreview, 1000); // Refresh every second
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
|
||||||
fetch('/camera/config')
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(data => {
|
|
||||||
console.log(data);
|
|
||||||
// data is grouped_params
|
|
||||||
const container = document.getElementById('camera-config-container');
|
|
||||||
container.innerHTML = '';
|
|
||||||
const captureSection = data.find(group => group.section === 'capturesettings');
|
|
||||||
if (captureSection) {
|
|
||||||
// Find the shutterspeed param
|
|
||||||
const shutterParam = captureSection.params.find(param => param.name === 'shutterspeed');
|
|
||||||
if (shutterParam) {
|
|
||||||
document.getElementById('display-camera-shutterspeed').textContent = shutterParam.current;
|
|
||||||
}
|
|
||||||
// find the aperture param
|
|
||||||
const apertureParam = captureSection.params.find(param => param.name === 'aperture');
|
|
||||||
if (apertureParam) {
|
|
||||||
document.getElementById('display-camera-aperture').textContent = apertureParam.current;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const imgSection = data.find(group => group.section === 'imgsettings');
|
|
||||||
if (imgSection) {
|
|
||||||
// Find the iso param
|
|
||||||
const isoParam = imgSection.params.find(param => param.name === 'iso');
|
|
||||||
if (isoParam) {
|
|
||||||
document.getElementById('display-camera-iso').textContent = isoParam.current;
|
|
||||||
}
|
|
||||||
// find the whitebalance param
|
|
||||||
const wbParam = imgSection.params.find(param => param.name === 'colortemperature');
|
|
||||||
if (wbParam) {
|
|
||||||
document.getElementById('display-camera-color-temperature').textContent = wbParam.current;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
data.forEach(group => {
|
|
||||||
const box = document.createElement('div');
|
|
||||||
box.className = 'box';
|
|
||||||
box.innerHTML = `<h2 class="subtitle has-text-weight-bold">${group.section.replace('settings', ' Settings')}</h2>`;
|
|
||||||
group.params.forEach(param => {
|
|
||||||
let field = document.createElement('div');
|
|
||||||
if (param.type == 'TEXT') {
|
|
||||||
field.className = 'text';
|
|
||||||
field.innerHTML = `
|
|
||||||
<p> ${param.name} </p>
|
|
||||||
`;
|
|
||||||
|
|
||||||
}else {
|
|
||||||
field.className = 'field';
|
|
||||||
field.innerHTML = `
|
|
||||||
<label class="label" for="${param.name}">${param.label}:</label>
|
|
||||||
<div class="control">
|
|
||||||
<div class="select is-fullwidth">
|
|
||||||
<select id="${param.name}" name="${param.name}">
|
|
||||||
${param.choices.map(choice =>
|
|
||||||
`<option value="${choice.value}" ${choice.label === param.current ? 'selected' : ''}>
|
|
||||||
${choice.label} (${choice.value})
|
|
||||||
</option>`
|
|
||||||
).join('')}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="control mt-2">
|
|
||||||
<button class="button is-small is-primary" type="button" onclick="setConfig('${param.name}')">Set</button>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
box.appendChild(field);
|
|
||||||
|
|
||||||
});
|
|
||||||
container.appendChild(box);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
function CapturePreview() {
|
|
||||||
fetch('/camera/capture_preview', {method: 'POST'})
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(data => {
|
|
||||||
if (data.status === 'ok') {
|
|
||||||
refreshPreview(); // Rafraîchir l'image après la capture
|
|
||||||
} else {
|
|
||||||
alert('Erreur lors de la capture : ' + (data.error || 'inconnue'));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
{% endblock extrajs %}
|
|
||||||
|
|
@ -1,74 +0,0 @@
|
||||||
{% extends "base.html" %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<section class="section">
|
|
||||||
<div class="container">
|
|
||||||
<h1 class="title">Conroler les LEDS</h1>
|
|
||||||
<div class="columns">
|
|
||||||
<div class="column is-half">
|
|
||||||
|
|
||||||
<div class="switch-slice">
|
|
||||||
<h2>LED 1</h2>
|
|
||||||
<label for="led-switch">OFF</label>
|
|
||||||
<label class="switch">
|
|
||||||
<input type="checkbox" id="led-switch-led1">
|
|
||||||
<span class="slider"></span>
|
|
||||||
</label>
|
|
||||||
<label for="led-switch">ON</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="switch-slice">
|
|
||||||
<h2>LED 2</h2>
|
|
||||||
<label for="led-switch">OFF</label>
|
|
||||||
<label class="switch">
|
|
||||||
<input type="checkbox" id="led-switch-led2">
|
|
||||||
<span class="slider"></span>
|
|
||||||
</label>
|
|
||||||
<label for="led-switch">ON</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="switch-slice">
|
|
||||||
<h2>LED 3</h2>
|
|
||||||
<label for="led-switch">OFF</label>
|
|
||||||
<label class="switch">
|
|
||||||
<input type="checkbox" id="led-switch-led3">
|
|
||||||
<span class="slider"></span>
|
|
||||||
</label>
|
|
||||||
<label for="led-switch">ON</label>
|
|
||||||
</div>
|
|
||||||
<div class="column">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
{% endblock content %}
|
|
||||||
|
|
||||||
{% block extrajs %}
|
|
||||||
<script>
|
|
||||||
function setLedState(ledName, state) {
|
|
||||||
fetch('/leds/set', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ led: ledName, state: state })
|
|
||||||
})
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(data => {
|
|
||||||
if (data.status !== 'ok') {
|
|
||||||
alert('Erreur lors du contrôle de la LED : ' + (data.error || 'inconnue'));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exemple d’attachement d’événement pour plusieurs LEDs
|
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
|
||||||
['led1', 'led2', 'led3'].forEach(function (ledName) {
|
|
||||||
const checkbox = document.getElementById('led-switch-' + ledName);
|
|
||||||
if (checkbox) {
|
|
||||||
checkbox.addEventListener('change', function () {
|
|
||||||
setLedState(ledName, checkbox.checked ? 'on' : 'off');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
{% endblock extrajs %}
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Strame ok mais stream imge fixe
|
|
||||||
#gphoto2 --stdout --capture-movie | ffmpeg -i - -c:v libx264 -f hls -hls_time 2 -hls_list_size 3 -hls_flags delete_segments src/nenuscanner/static/stream.m3u8
|
|
||||||
|
|
||||||
|
|
||||||
gphoto2 --capture-image-and-download -F 0 -I 2 --stdout | ffmpeg -f image2pipe -framerate 0.5 -i - -r 25 -c:v libx264 -f hls -hls_time 2 -hls_list_size 3 -hls_flags delete_segments -g 50 -keyint_min 50 src/nenuscanner/static/stream.m3u8
|
|
||||||
Loading…
Reference in New Issue