Updates Leds pages
This commit is contained in:
parent
13c9bdfd25
commit
84a51af78d
|
|
@ -15,6 +15,8 @@ class Leds:
|
|||
|
||||
def off(self):
|
||||
pass
|
||||
|
||||
|
||||
|
||||
|
||||
class GpioLed:
|
||||
|
|
@ -65,7 +67,18 @@ class GpioLeds(Leds):
|
|||
def on(self):
|
||||
for led in self.leds:
|
||||
led.on()
|
||||
|
||||
def enter(self):
|
||||
return self.__enter__()
|
||||
|
||||
def exit(self,*args):
|
||||
self.__exit__(*args)
|
||||
|
||||
def get_by_uuid(self, uuid: int) -> GpioLed:
|
||||
for led in self.leds:
|
||||
if led.gpio_pin == uuid:
|
||||
return led
|
||||
raise ValueError(f"No LED with UUID {uuid}")
|
||||
|
||||
class DummyLed:
|
||||
def __init__(self, gpio_pin: int):
|
||||
|
|
@ -110,10 +123,22 @@ class DummyLeds(Leds):
|
|||
def on(self):
|
||||
for led in self.leds:
|
||||
led.on()
|
||||
|
||||
def enter(self):
|
||||
return self.__enter__()
|
||||
|
||||
def exit(self,*args):
|
||||
self.__exit__(*args)
|
||||
|
||||
def get_by_uuid(self, uuid: int) -> DummyLed:
|
||||
for led in self.leds:
|
||||
if led.gpio_pin == uuid:
|
||||
return led
|
||||
raise ValueError(f"No LED with UUID {uuid}")
|
||||
|
||||
|
||||
_leds = GpioLeds(config.GPIO_CHIP, config.LEDS_UUIDS) if config.GPIO_CHIP is not None else DummyLeds(config.LEDS_UUIDS)
|
||||
|
||||
|
||||
def get() -> Leds:
|
||||
return _leds
|
||||
return _leds
|
||||
|
|
@ -1,13 +1,45 @@
|
|||
from flask import Blueprint, render_template, request, send_file, jsonify
|
||||
from flask import Blueprint, render_template, request, send_file, jsonify, session, current_app
|
||||
import json
|
||||
import subprocess
|
||||
|
||||
from .. import camera as C
|
||||
from .. import leds
|
||||
|
||||
from .. import leds,config
|
||||
|
||||
blueprint = Blueprint('leds', __name__)
|
||||
|
||||
# WARNING: This is a temporary global variable to hold the state of the GPIO LEDs.
|
||||
# This is necessary because the LED state must persist across multiple requests,
|
||||
# and Flask does not maintain state between requests.
|
||||
# A better solution would be to implement a proper state management system.
|
||||
|
||||
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]
|
||||
|
||||
@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
|
||||
|
||||
|
||||
|
|
@ -16,9 +48,15 @@ def get():
|
|||
"""
|
||||
Returns the pages showing all leds.
|
||||
"""
|
||||
gpio_leds = _get_gpio_leds()
|
||||
|
||||
print(gpio_leds)
|
||||
|
||||
for i, led in enumerate(gpio_leds.leds):
|
||||
print(f"LED {i}: {led}, is_on={led.is_on}")
|
||||
return render_template(
|
||||
'leds.html')
|
||||
'leds.html', leds= config.LEDS_UUIDS)
|
||||
|
||||
|
||||
@blueprint.route('/set', methods=['POST'])
|
||||
def set_led():
|
||||
|
|
@ -29,17 +67,18 @@ def set_led():
|
|||
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":
|
||||
# get the controller (lazy, stored on app.extensions)
|
||||
gpio_leds = _get_gpio_leds()
|
||||
|
||||
try:
|
||||
# parse led id/name according to your naming convention
|
||||
gpio_led = gpio_leds.get_by_uuid(int(led))
|
||||
print(f"Setting {led} / {gpio_led} to {state}")
|
||||
if state == "on":
|
||||
gpio_led.on()
|
||||
else:
|
||||
else:
|
||||
gpio_led.off()
|
||||
|
||||
|
||||
print(f"Commande reçue pour {led} : {state}")
|
||||
except Exception as e:
|
||||
return jsonify({'status': 'error', 'error': str(e)}), 400
|
||||
|
||||
return jsonify({'status': 'ok', 'led': led, 'state': state})
|
||||
|
|
@ -2,73 +2,138 @@
|
|||
|
||||
{% 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="container">
|
||||
<h1 class="title">Contrôler les LEDS</h1>
|
||||
|
||||
<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>
|
||||
<div id="leds-container" class="columns is-multiline">
|
||||
<!-- Les switches seront créés à la volée par JS à partir de la variable `leds` -->
|
||||
</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'));
|
||||
}
|
||||
});
|
||||
}
|
||||
<style>
|
||||
/* switch simple */
|
||||
.switch-slice {
|
||||
display:flex;
|
||||
align-items:center;
|
||||
gap:0.5rem;
|
||||
}
|
||||
.switch {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 46px;
|
||||
height: 24px;
|
||||
}
|
||||
.switch input { display: none; }
|
||||
.slider {
|
||||
position:absolute; inset:0; cursor:pointer;
|
||||
background:#ddd; border-radius:24px; transition:.2s;
|
||||
}
|
||||
.slider:before {
|
||||
content:""; position:absolute; height:18px; width:18px; left:3px; top:3px;
|
||||
background:#fff; border-radius:50%; transition:.2s;
|
||||
}
|
||||
.switch input:checked + .slider { background:#48c774; }
|
||||
.switch input:checked + .slider:before { transform: translateX(22px); }
|
||||
/* remove fixed width so Bulma columns control layout */
|
||||
.led-box { padding:0.75rem; max-width: 100%; }
|
||||
.led-name { font-weight:600; margin-bottom:.35rem; }
|
||||
</style>
|
||||
|
||||
// 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>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
// leds list injected by Jinja
|
||||
const leds = {{ leds | tojson | safe }};
|
||||
|
||||
const container = document.getElementById('leds-container');
|
||||
|
||||
// helper: send state to server
|
||||
async function setLedState(ledName, state, checkbox) {
|
||||
try {
|
||||
const res = await fetch('/leds/set', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ led: ledName, state: state })
|
||||
});
|
||||
const json = await res.json();
|
||||
if (res.ok && json.status === 'ok') {
|
||||
checkbox.classList.remove('has-text-danger');
|
||||
checkbox.classList.add('has-text-success');
|
||||
setTimeout(()=>{ checkbox.classList.remove('has-text-success'); }, 500);
|
||||
} else {
|
||||
console.error('LED set error', json);
|
||||
alert('Erreur commande LED: ' + (json.error || res.status));
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
alert('Erreur réseau lors de la commande LED');
|
||||
}
|
||||
}
|
||||
|
||||
// create a switch card for each led name/uuid
|
||||
leds.forEach(function(ledName, idx) {
|
||||
// Use Bulma column classes to get max 3 per row on desktop
|
||||
const col = document.createElement('div');
|
||||
col.className = 'column is-one-third-desktop is-half-tablet is-full-mobile led-box';
|
||||
|
||||
const box = document.createElement('div');
|
||||
|
||||
const label = document.createElement('div');
|
||||
label.className = 'led-name';
|
||||
label.textContent = ledName;
|
||||
box.appendChild(label);
|
||||
|
||||
const switchRow = document.createElement('div');
|
||||
switchRow.className = 'switch-slice';
|
||||
|
||||
const offLabel = document.createElement('span');
|
||||
offLabel.textContent = 'OFF';
|
||||
offLabel.style.fontSize = '0.9rem';
|
||||
|
||||
const switchLabel = document.createElement('label');
|
||||
switchLabel.className = 'switch';
|
||||
|
||||
const input = document.createElement('input');
|
||||
input.type = 'checkbox';
|
||||
input.id = 'led-switch-' + idx;
|
||||
input.dataset.led = ledName;
|
||||
|
||||
const slider = document.createElement('span');
|
||||
slider.className = 'slider';
|
||||
|
||||
switchLabel.appendChild(input);
|
||||
switchLabel.appendChild(slider);
|
||||
|
||||
const onLabel = document.createElement('span');
|
||||
onLabel.textContent = 'ON';
|
||||
onLabel.style.fontSize = '0.9rem';
|
||||
|
||||
switchRow.appendChild(offLabel);
|
||||
switchRow.appendChild(switchLabel);
|
||||
switchRow.appendChild(onLabel);
|
||||
|
||||
box.appendChild(switchRow);
|
||||
|
||||
// Optional small status text
|
||||
const status = document.createElement('div');
|
||||
status.style.marginTop = '0.5rem';
|
||||
status.style.fontSize = '0.85rem';
|
||||
status.textContent = 'état: inconnu';
|
||||
box.appendChild(status);
|
||||
|
||||
col.appendChild(box);
|
||||
container.appendChild(col);
|
||||
|
||||
// event
|
||||
input.addEventListener('change', function (ev) {
|
||||
const led = ev.target.dataset.led;
|
||||
const state = ev.target.checked ? 'on' : 'off';
|
||||
status.textContent = 'état: ' + state;
|
||||
setLedState(led, state, status);
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock extrajs %}
|
||||
Loading…
Reference in New Issue