Mini menu client + luminosite impression + endpoints systeme
- Menu client coin haut-gauche : WiFi, luminosite, relancer, redemarrer, eteindre - Popup WiFi : scan, statut, connexion (reutilise les endpoints existants) - Popup luminosite : slider -50% a +50%, ajuste la luminosite avant impression (ImageEnhance.Brightness dans _preparer_image) - Endpoints systeme : /api/systeme/eteindre, redemarrer, redemarrer-app - Endpoint exposition Canon : /api/camera/exposition (GET choix + POST valeur) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -495,10 +495,30 @@ async def _reconnecter_dslr_avec_reset(echecs: int):
|
||||
|
||||
|
||||
def _appliquer_config_camera():
|
||||
"""Applique les paramètres caméra persistants (flash, etc.) après connexion."""
|
||||
"""Applique les paramètres caméra persistants (flash, exposition) après connexion."""
|
||||
cfg = charger_config()
|
||||
flash_integre = cfg.get("camera", {}).get("flash_integre", True)
|
||||
camera.configurer_flash(flash_integre)
|
||||
ev = cfg.get("camera", {}).get("exposure_compensation")
|
||||
if ev is not None:
|
||||
_set_canon_config("exposurecompensation", str(ev))
|
||||
|
||||
|
||||
def _set_canon_config(nom: str, valeur):
|
||||
"""Applique un réglage gphoto2 sur le Canon."""
|
||||
if camera.mode != "gphoto2" or not camera.connectee:
|
||||
return False
|
||||
try:
|
||||
with camera._gp_lock:
|
||||
cfg = camera.camera.get_config()
|
||||
w = cfg.get_child_by_name(nom)
|
||||
w.set_value(valeur)
|
||||
camera.camera.set_config(cfg)
|
||||
log.info(f"Canon config: {nom} = {valeur}")
|
||||
return True
|
||||
except Exception as e:
|
||||
log.warning(f"Canon config {nom} échoué: {e}")
|
||||
return False
|
||||
|
||||
|
||||
async def _watchdog_systemd():
|
||||
@@ -1260,6 +1280,55 @@ async def api_camera_reconnecter(body: dict = {}):
|
||||
return {"connectee": ok, "mode": camera.mode}
|
||||
|
||||
|
||||
@app.post("/api/camera/exposition")
|
||||
async def api_camera_exposition(body: dict):
|
||||
"""Ajuste la compensation d'exposition Canon (-3 à +3 EV)."""
|
||||
valeur = body.get("valeur", "0")
|
||||
ok = _set_canon_config("exposurecompensation", str(valeur))
|
||||
if ok:
|
||||
mettre_a_jour_config({"camera": {"exposure_compensation": str(valeur)}})
|
||||
return {"succes": ok, "valeur": valeur}
|
||||
|
||||
|
||||
@app.get("/api/camera/exposition")
|
||||
async def api_camera_exposition_get():
|
||||
"""Retourne les valeurs possibles et la valeur actuelle d'exposition."""
|
||||
result = {"valeur": "0", "choix": []}
|
||||
if camera.mode == "gphoto2" and camera.connectee:
|
||||
try:
|
||||
with camera._gp_lock:
|
||||
cfg = camera.camera.get_config()
|
||||
w = cfg.get_child_by_name("exposurecompensation")
|
||||
result["valeur"] = w.get_value()
|
||||
result["choix"] = [w.get_choice(i) for i in range(w.count_choices())]
|
||||
except Exception as e:
|
||||
result["erreur"] = str(e)
|
||||
return result
|
||||
|
||||
|
||||
# --- API Système (menu client) ---
|
||||
|
||||
@app.post("/api/systeme/eteindre")
|
||||
async def api_eteindre():
|
||||
import subprocess
|
||||
subprocess.Popen(["sudo", "shutdown", "-h", "now"])
|
||||
return {"succes": True, "message": "Extinction en cours..."}
|
||||
|
||||
|
||||
@app.post("/api/systeme/redemarrer")
|
||||
async def api_redemarrer():
|
||||
import subprocess
|
||||
subprocess.Popen(["sudo", "reboot"])
|
||||
return {"succes": True, "message": "Redémarrage en cours..."}
|
||||
|
||||
|
||||
@app.post("/api/systeme/redemarrer-app")
|
||||
async def api_redemarrer_app():
|
||||
import subprocess, signal
|
||||
os.kill(os.getpid(), signal.SIGTERM)
|
||||
return {"succes": True}
|
||||
|
||||
|
||||
# --- API Effets ---
|
||||
|
||||
@app.get("/api/filtres")
|
||||
|
||||
@@ -111,6 +111,13 @@ def _preparer_image(chemin: Path, largeur: int, hauteur: int,
|
||||
if config_imp.get("rotation_180", False) and not skip_rotate:
|
||||
img = img.rotate(180)
|
||||
|
||||
luminosite = config_imp.get("luminosite_impression", 0)
|
||||
if luminosite != 0:
|
||||
from PIL import ImageEnhance
|
||||
facteur = 1.0 + luminosite / 100.0
|
||||
img = ImageEnhance.Brightness(img).enhance(facteur)
|
||||
log.debug(f"Luminosité impression ajustée : {luminosite}% (facteur {facteur:.2f})")
|
||||
|
||||
tmp = tempfile.NamedTemporaryFile(suffix=".jpg", delete=False)
|
||||
img.save(tmp.name, "JPEG", quality=95, dpi=(300, 300))
|
||||
log.debug(f"Image préparée : {orientation} {largeur}x{hauteur}px → {tmp.name}")
|
||||
|
||||
@@ -157,6 +157,107 @@ html, body {
|
||||
color: rgba(255,255,255,0.4);
|
||||
}
|
||||
|
||||
/* Mini menu client */
|
||||
.btn-menu-client {
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
left: 12px;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 1.3rem;
|
||||
color: rgba(255,255,255,0.2);
|
||||
cursor: pointer;
|
||||
border-radius: 50%;
|
||||
transition: color 0.3s;
|
||||
z-index: 60;
|
||||
}
|
||||
.btn-menu-client:active {
|
||||
color: rgba(255,255,255,0.5);
|
||||
}
|
||||
.menu-client {
|
||||
position: absolute;
|
||||
top: 60px;
|
||||
left: 12px;
|
||||
background: rgba(0,0,0,0.92);
|
||||
border-radius: 16px;
|
||||
padding: 16px;
|
||||
z-index: 200;
|
||||
min-width: 220px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
.menu-client button {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 14px 16px;
|
||||
font-size: 1rem;
|
||||
text-align: left;
|
||||
background: rgba(255,255,255,0.08);
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.menu-client button:active {
|
||||
background: rgba(255,255,255,0.2);
|
||||
}
|
||||
.menu-client-titre {
|
||||
font-size: .85rem;
|
||||
font-weight: 600;
|
||||
color: rgba(255,255,255,0.5);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
padding: 0 4px 8px;
|
||||
}
|
||||
.popup-wifi {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(0,0,0,0.85);
|
||||
z-index: 300;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.popup-wifi-inner {
|
||||
background: rgba(30,30,30,0.98);
|
||||
border-radius: 20px;
|
||||
padding: 24px;
|
||||
width: 90%;
|
||||
max-width: 400px;
|
||||
}
|
||||
.popup-wifi-inner button {
|
||||
padding: 12px;
|
||||
font-size: 1rem;
|
||||
background: rgba(255,255,255,0.1);
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.popup-wifi-inner button:active {
|
||||
background: rgba(255,255,255,0.2);
|
||||
}
|
||||
.wifi-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 12px;
|
||||
margin: 4px 0;
|
||||
background: rgba(255,255,255,0.06);
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.wifi-item:active {
|
||||
background: rgba(255,255,255,0.15);
|
||||
}
|
||||
.wifi-item.actif {
|
||||
border-left: 3px solid #4caf50;
|
||||
}
|
||||
|
||||
/* Popup mot de passe */
|
||||
.popup-overlay {
|
||||
position: fixed;
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
|
||||
<meta name="google" content="notranslate">
|
||||
<meta http-equiv="Content-Language" content="fr">
|
||||
<link rel="stylesheet" href="/css/style.css?v=16">
|
||||
<link rel="stylesheet" href="/css/style.css?v=17">
|
||||
<link rel="stylesheet" href="/css/themes.css?v=2">
|
||||
</head>
|
||||
<body>
|
||||
@@ -59,6 +59,38 @@
|
||||
<div id="btn-admin" class="btn-admin" onclick="ouvrirAdmin()">⚙</div>
|
||||
<!-- Icone photostation coin bas-gauche (visible seulement si installée) -->
|
||||
<div id="btn-photostation" class="btn-photostation cache" onclick="ouvrirPhotostation()">🖼</div>
|
||||
<!-- Mini menu client coin haut-gauche -->
|
||||
<div id="btn-menu-client" class="btn-menu-client" onclick="toggleMenuClient()">☰</div>
|
||||
<div id="menu-client" class="menu-client cache">
|
||||
<div class="menu-client-titre">Maintenance</div>
|
||||
<button onclick="menuClientAction('wifi')">📶 WiFi</button>
|
||||
<button onclick="menuClientAction('exposition')">☀ Luminosite</button>
|
||||
<button onclick="menuClientAction('restart-app')">🔄 Relancer l'appli</button>
|
||||
<button onclick="menuClientAction('reboot')">🔃 Redemarrer</button>
|
||||
<button onclick="menuClientAction('shutdown')">⏻ Eteindre</button>
|
||||
<button onclick="toggleMenuClient()" style="background:transparent;color:var(--text2)">Fermer</button>
|
||||
</div>
|
||||
<!-- Popup WiFi -->
|
||||
<div id="popup-wifi" class="popup-wifi cache">
|
||||
<div class="popup-wifi-inner">
|
||||
<div class="menu-client-titre">WiFi</div>
|
||||
<div id="wifi-status"></div>
|
||||
<div id="wifi-list" style="max-height:300px;overflow-y:auto"></div>
|
||||
<button onclick="scanWifi()" style="margin-top:8px;width:100%">Actualiser</button>
|
||||
<button onclick="document.getElementById('popup-wifi').classList.add('cache')" style="margin-top:4px;width:100%;background:transparent;color:var(--text2)">Fermer</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Popup Exposition -->
|
||||
<div id="popup-exposition" class="popup-wifi cache">
|
||||
<div class="popup-wifi-inner">
|
||||
<div class="menu-client-titre">Luminosite photo</div>
|
||||
<div id="expo-status" style="text-align:center;margin:12px 0;font-size:1.5rem;font-weight:700"></div>
|
||||
<input type="range" id="expo-slider" min="-9" max="9" value="0" style="width:100%" oninput="updateExpoLabel(this.value)">
|
||||
<div id="expo-label" style="text-align:center;margin:8px 0;font-size:.9rem;color:var(--text2)">0</div>
|
||||
<button onclick="appliquerExpo()" style="width:100%">Appliquer</button>
|
||||
<button onclick="document.getElementById('popup-exposition').classList.add('cache')" style="margin-top:4px;width:100%;background:transparent;color:var(--text2)">Fermer</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Choix du mode -->
|
||||
@@ -1145,7 +1177,7 @@
|
||||
</style>
|
||||
|
||||
<script src="/js/websocket.js?v=16"></script>
|
||||
<script src="/js/app.js?v=25"></script>
|
||||
<script src="/js/app.js?v=26"></script>
|
||||
<script src="/js/camera.js?v=26"></script>
|
||||
<script src="/js/effects.js?v=4"></script>
|
||||
<script src="/js/gallery.js?v=5"></script>
|
||||
|
||||
@@ -707,6 +707,91 @@ function eteindreSpots() {
|
||||
fetch('/api/relais/veille', { method: 'POST' }).catch(() => {});
|
||||
}
|
||||
|
||||
// --- Mini menu client ---
|
||||
|
||||
function toggleMenuClient() {
|
||||
document.getElementById('menu-client').classList.toggle('cache');
|
||||
}
|
||||
|
||||
async function menuClientAction(action) {
|
||||
document.getElementById('menu-client').classList.add('cache');
|
||||
if (action === 'wifi') {
|
||||
document.getElementById('popup-wifi').classList.remove('cache');
|
||||
scanWifi();
|
||||
} else if (action === 'exposition') {
|
||||
document.getElementById('popup-exposition').classList.remove('cache');
|
||||
chargerExposition();
|
||||
} else if (action === 'shutdown') {
|
||||
if (confirm('Eteindre la borne ?')) apiPost('/api/systeme/eteindre');
|
||||
} else if (action === 'reboot') {
|
||||
if (confirm('Redemarrer la borne ?')) apiPost('/api/systeme/redemarrer');
|
||||
} else if (action === 'restart-app') {
|
||||
apiPost('/api/systeme/redemarrer-app');
|
||||
setTimeout(() => location.reload(), 3000);
|
||||
}
|
||||
}
|
||||
|
||||
async function scanWifi() {
|
||||
const list = document.getElementById('wifi-list');
|
||||
const status = document.getElementById('wifi-status');
|
||||
list.innerHTML = '<div style="text-align:center;padding:16px;color:var(--text2)">Scan en cours...</div>';
|
||||
try {
|
||||
const s = await apiGet('/api/wifi/status');
|
||||
status.innerHTML = s.connecte
|
||||
? `<div style="padding:8px;color:#4caf50">Connecte a <b>${s.ssid}</b> (${s.signal || '?'}%)</div>`
|
||||
: '<div style="padding:8px;color:#f44336">Non connecte</div>';
|
||||
const nets = await apiGet('/api/wifi/scan');
|
||||
if (!nets.length) { list.innerHTML = '<div style="padding:12px;color:var(--text2)">Aucun reseau</div>'; return; }
|
||||
list.innerHTML = nets.map(n => `
|
||||
<div class="wifi-item${n.actif ? ' actif' : ''}" onclick="connecterWifi('${n.ssid.replace(/'/g,"\\'")}', ${n.enregistre})">
|
||||
<div>
|
||||
<div style="font-weight:600">${n.ssid}</div>
|
||||
<div style="font-size:.75rem;color:var(--text2)">${n.signal}% ${n.securise ? '🔒' : ''} ${n.enregistre ? '(enregistre)' : ''}</div>
|
||||
</div>
|
||||
${n.actif ? '<span style="color:#4caf50;font-weight:700">✓</span>' : ''}
|
||||
</div>
|
||||
`).join('');
|
||||
} catch(e) { list.innerHTML = '<div style="padding:12px;color:#f44336">Erreur: ' + e + '</div>'; }
|
||||
}
|
||||
|
||||
async function connecterWifi(ssid, enregistre) {
|
||||
if (enregistre) {
|
||||
const r = await apiPost('/api/wifi/connect', {ssid});
|
||||
alert(r.message || (r.succes ? 'Connecte' : 'Erreur'));
|
||||
scanWifi();
|
||||
return;
|
||||
}
|
||||
const mdp = prompt('Mot de passe WiFi pour ' + ssid + ' :');
|
||||
if (mdp === null) return;
|
||||
const r = await apiPost('/api/wifi/connect', {ssid, password: mdp});
|
||||
alert(r.message || (r.succes ? 'Connecte' : 'Erreur'));
|
||||
scanWifi();
|
||||
}
|
||||
|
||||
async function chargerExposition() {
|
||||
const cfg = charger_config ? charger_config() : config;
|
||||
const luminosite = (cfg || config).impression?.luminosite_impression || 0;
|
||||
document.getElementById('expo-slider').value = luminosite;
|
||||
document.getElementById('expo-slider').min = -50;
|
||||
document.getElementById('expo-slider').max = 50;
|
||||
document.getElementById('expo-slider').step = 5;
|
||||
updateExpoLabel(luminosite);
|
||||
}
|
||||
|
||||
function updateExpoLabel(val) {
|
||||
const signe = val > 0 ? '+' : '';
|
||||
document.getElementById('expo-label').textContent = `${signe}${val}%`;
|
||||
document.getElementById('expo-status').textContent = val == 0 ? 'Normal' : `${signe}${val}%`;
|
||||
}
|
||||
|
||||
async function appliquerExpo() {
|
||||
const val = parseInt(document.getElementById('expo-slider').value);
|
||||
await apiPost('/api/config', {impression: {luminosite_impression: val}});
|
||||
document.getElementById('popup-exposition').classList.add('cache');
|
||||
afficherStatut('Luminosite impression : ' + (val > 0 ? '+' : '') + val + '%', 'succes');
|
||||
config = await apiGet('/api/config');
|
||||
}
|
||||
|
||||
// Demarrage
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
init().then(() => {
|
||||
|
||||
Reference in New Issue
Block a user