Updates Leds pages
This commit is contained in:
parent
13c9bdfd25
commit
84a51af78d
|
|
@ -15,6 +15,8 @@ class Leds:
|
||||||
|
|
||||||
def off(self):
|
def off(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class GpioLed:
|
class GpioLed:
|
||||||
|
|
@ -65,7 +67,18 @@ class GpioLeds(Leds):
|
||||||
def on(self):
|
def on(self):
|
||||||
for led in self.leds:
|
for led in self.leds:
|
||||||
led.on()
|
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:
|
class DummyLed:
|
||||||
def __init__(self, gpio_pin: int):
|
def __init__(self, gpio_pin: int):
|
||||||
|
|
@ -110,10 +123,22 @@ class DummyLeds(Leds):
|
||||||
def on(self):
|
def on(self):
|
||||||
for led in self.leds:
|
for led in self.leds:
|
||||||
led.on()
|
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)
|
_leds = GpioLeds(config.GPIO_CHIP, config.LEDS_UUIDS) if config.GPIO_CHIP is not None else DummyLeds(config.LEDS_UUIDS)
|
||||||
|
|
||||||
|
|
||||||
def get() -> Leds:
|
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 json
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
from .. import camera as C
|
from .. import camera as C
|
||||||
from .. import leds
|
from .. import leds,config
|
||||||
|
|
||||||
|
|
||||||
blueprint = Blueprint('leds', __name__)
|
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
|
# Routes for object management
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -16,9 +48,15 @@ def get():
|
||||||
"""
|
"""
|
||||||
Returns the pages showing all leds.
|
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(
|
return render_template(
|
||||||
'leds.html')
|
'leds.html', leds= config.LEDS_UUIDS)
|
||||||
|
|
||||||
|
|
||||||
@blueprint.route('/set', methods=['POST'])
|
@blueprint.route('/set', methods=['POST'])
|
||||||
def set_led():
|
def set_led():
|
||||||
|
|
@ -29,17 +67,18 @@ def set_led():
|
||||||
data = request.get_json()
|
data = request.get_json()
|
||||||
led = data.get('led')
|
led = data.get('led')
|
||||||
state = data.get('state')
|
state = data.get('state')
|
||||||
ledId=int(led[3])
|
# get the controller (lazy, stored on app.extensions)
|
||||||
|
gpio_leds = _get_gpio_leds()
|
||||||
with leds.get() as gpio_leds:
|
|
||||||
print(gpio_leds.leds)
|
try:
|
||||||
gpio_led=gpio_leds.leds[ledId]
|
# parse led id/name according to your naming convention
|
||||||
if state == "on":
|
gpio_led = gpio_leds.get_by_uuid(int(led))
|
||||||
|
print(f"Setting {led} / {gpio_led} to {state}")
|
||||||
|
if state == "on":
|
||||||
gpio_led.on()
|
gpio_led.on()
|
||||||
else:
|
else:
|
||||||
gpio_led.off()
|
gpio_led.off()
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({'status': 'error', 'error': str(e)}), 400
|
||||||
print(f"Commande reçue pour {led} : {state}")
|
|
||||||
|
|
||||||
return jsonify({'status': 'ok', 'led': led, 'state': state})
|
return jsonify({'status': 'ok', 'led': led, 'state': state})
|
||||||
|
|
@ -2,73 +2,138 @@
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<section class="section">
|
<section class="section">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h1 class="title">Conroler les LEDS</h1>
|
<h1 class="title">Contrôler les LEDS</h1>
|
||||||
<div class="columns">
|
|
||||||
<div class="column is-half">
|
|
||||||
|
|
||||||
<div class="switch-slice">
|
<div id="leds-container" class="columns is-multiline">
|
||||||
<h2>LED 1</h2>
|
<!-- Les switches seront créés à la volée par JS à partir de la variable `leds` -->
|
||||||
<label for="led-switch">OFF</label>
|
</div>
|
||||||
<label class="switch">
|
</div>
|
||||||
<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>
|
</section>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|
||||||
{% block extrajs %}
|
{% block extrajs %}
|
||||||
<script>
|
<style>
|
||||||
function setLedState(ledName, state) {
|
/* switch simple */
|
||||||
fetch('/leds/set', {
|
.switch-slice {
|
||||||
method: 'POST',
|
display:flex;
|
||||||
headers: { 'Content-Type': 'application/json' },
|
align-items:center;
|
||||||
body: JSON.stringify({ led: ledName, state: state })
|
gap:0.5rem;
|
||||||
})
|
}
|
||||||
.then(response => response.json())
|
.switch {
|
||||||
.then(data => {
|
position: relative;
|
||||||
if (data.status !== 'ok') {
|
display: inline-block;
|
||||||
alert('Erreur lors du contrôle de la LED : ' + (data.error || 'inconnue'));
|
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
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
['led1', 'led2', 'led3'].forEach(function (ledName) {
|
// leds list injected by Jinja
|
||||||
const checkbox = document.getElementById('led-switch-' + ledName);
|
const leds = {{ leds | tojson | safe }};
|
||||||
if (checkbox) {
|
|
||||||
checkbox.addEventListener('change', function () {
|
const container = document.getElementById('leds-container');
|
||||||
setLedState(ledName, checkbox.checked ? 'on' : 'off');
|
|
||||||
});
|
// 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>
|
</script>
|
||||||
{% endblock extrajs %}
|
{% endblock extrajs %}
|
||||||
Loading…
Reference in New Issue