From 5686706a4e9b89098701391cd5bbac878a0f022a Mon Sep 17 00:00:00 2001 From: Jo Date: Tue, 7 Jul 2026 11:40:52 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20Home=20Assistant=20integration=20?= =?UTF-8?q?=E2=80=94=20live=20status=20+=20auto-sync=20print=20slots?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- __pycache__/app.cpython-310.pyc | Bin 18447 -> 82670 bytes app.py | 144 ++++++++++++++++++++++++++++++-- templates/planning.html | 75 +++++++++++++++++ templates/printers.html | 15 +++- templates/settings.html | 53 ++++++++++++ 5 files changed, 281 insertions(+), 6 deletions(-) diff --git a/__pycache__/app.cpython-310.pyc b/__pycache__/app.cpython-310.pyc index db6855d0e128c0cb8a7d8ea00ec16f7dd636eb55..596867eee646ddc37f7086d76c870e6d773a9013 100644 GIT binary patch literal 82670 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 diff --git a/app.py b/app.py index 31546c4..8b31023 100644 --- a/app.py +++ b/app.py @@ -1,5 +1,9 @@ from flask import Flask, render_template, request, redirect, url_for, jsonify, Response, session, flash -import sqlite3, csv, io, os, zipfile, re, math, secrets, functools +import sqlite3, csv, io, os, zipfile, re, math, secrets, functools, threading, time +try: + import requests as _requests +except ImportError: + _requests = None from datetime import datetime, timedelta, date as date_cls from authlib.integrations.flask_client import OAuth @@ -220,6 +224,8 @@ def init_db(): ('nc_app_password',''), ('nc_root_path','/3D'), ('ical_token',''), + ('ha_url',''), + ('ha_token',''), ] conn.executemany('INSERT OR IGNORE INTO settings VALUES (?,?)', defaults) @@ -260,6 +266,7 @@ def init_db(): 'ALTER TABLE materials ADD COLUMN total_wasted_g REAL DEFAULT 0', 'ALTER TABLE jobs ADD COLUMN client_token TEXT DEFAULT NULL', 'ALTER TABLE jobs ADD COLUMN plate_name TEXT DEFAULT ""', + 'ALTER TABLE printers ADD COLUMN ha_entity_prefix TEXT DEFAULT ""', ] for sql in migrations: try: @@ -1313,10 +1320,12 @@ def planning(): def printers_page(): conn = get_db() if request.method == 'POST': - name = request.form['name'] - active = 1 if request.form.get('is_active') else 0 - notes = request.form.get('notes','') - conn.execute('INSERT INTO printers(name,is_active,notes) VALUES(?,?,?)', (name,active,notes)) + name = request.form['name'] + active = 1 if request.form.get('is_active') else 0 + notes = request.form.get('notes','') + ha_prefix = request.form.get('ha_entity_prefix','').strip().lower() + conn.execute('INSERT INTO printers(name,is_active,notes,ha_entity_prefix) VALUES(?,?,?,?)', + (name, active, notes, ha_prefix)) conn.commit() printers = conn.execute('SELECT * FROM printers ORDER BY id').fetchall() conn.close() @@ -1907,6 +1916,130 @@ def calendar_ics(): return Response(ical, mimetype='text/calendar; charset=utf-8', headers={'Content-Disposition': 'inline; filename=planning_3d.ics'}) + +# ─── Home Assistant — Sync ──────────────────────────────────────────────────── + +def _ha_fetch_all_states(ha_url, ha_token): + """Retourne dict {entity_id: state_str} depuis l'API HA.""" + if not _requests: + return {} + headers = {'Authorization': f'Bearer {ha_token}'} + r = _requests.get(f'{ha_url}/api/states', headers=headers, timeout=15) + if r.status_code != 200: + return {} + return {s['entity_id']: s['state'] for s in r.json()} + + +def _ha_sync_once(): + """Poll HA et met a jour les print_slots actifs.""" + try: + s = get_settings() + ha_url = str(s.get('ha_url', '')).rstrip('/') + ha_token = str(s.get('ha_token', '')) + if not ha_url or not ha_token: + return + states = _ha_fetch_all_states(ha_url, ha_token) + if not states: + return + conn = get_db() + printers = conn.execute( + "SELECT id, name, ha_entity_prefix FROM printers " + "WHERE ha_entity_prefix IS NOT NULL AND ha_entity_prefix != ''" + ).fetchall() + now_iso = datetime.utcnow().isoformat() + window = (datetime.utcnow() + timedelta(minutes=30)).isoformat() + for p in printers: + prefix = p['ha_entity_prefix'] + ha_status = states.get(f'sensor.{prefix}_etat_de_l_impression', 'unknown') + ha_error = states.get(f'binary_sensor.{prefix}_erreur_d_impression', 'off') + slot = conn.execute( + "SELECT id, status FROM print_slots " + "WHERE printer_id=? AND status IN ('planned','running') " + "AND planned_start <= ? AND planned_end >= ? " + "ORDER BY planned_start DESC LIMIT 1", + (p['id'], window, now_iso) + ).fetchone() + if not slot: + continue + new_status = None + if ha_error == 'on': + new_status = 'failed' + elif ha_status == 'finish': + new_status = 'done' + elif ha_status == 'running' and slot['status'] == 'planned': + new_status = 'running' + if new_status: + conn.execute('UPDATE print_slots SET status=? WHERE id=?', + (new_status, slot['id'])) + conn.commit() + conn.close() + except Exception as e: + print(f'[HA sync] {e}') + + +def _start_ha_poll(): + """Lance le thread de polling HA en arriere-plan.""" + def _loop(): + time.sleep(10) + while True: + _ha_sync_once() + time.sleep(120) + t = threading.Thread(target=_loop, daemon=True, name='ha-poll') + t.start() + + +@app.route('/api/ha/status') +@login_required +def api_ha_status(): + """Retourne le statut live HA pour toutes les imprimantes configurees.""" + s = get_settings() + ha_url = str(s.get('ha_url', '')).rstrip('/') + ha_token = str(s.get('ha_token', '')) + if not ha_url or not ha_token: + return jsonify({'error': 'HA non configure', 'printers': {}}) + try: + if not _requests: + return jsonify({'error': 'requests non installe', 'printers': {}}) + headers = {'Authorization': f'Bearer {ha_token}'} + r = _requests.get(f'{ha_url}/api/states', headers=headers, timeout=10) + if r.status_code != 200: + return jsonify({'error': f'HA HTTP {r.status_code}', 'printers': {}}) + raw_states = r.json() + states = {st['entity_id']: st['state'] for st in raw_states} + conn = get_db() + printers = conn.execute( + "SELECT id, name, ha_entity_prefix FROM printers " + "WHERE ha_entity_prefix IS NOT NULL AND ha_entity_prefix != ''" + ).fetchall() + conn.close() + result = {} + for p in printers: + pfx = p['ha_entity_prefix'] + def gs(suf, default='unknown', _p=pfx): + return states.get(f'sensor.{_p}_{suf}', default) + def gb(suf, _p=pfx): + return states.get(f'binary_sensor.{_p}_{suf}', 'off') + try: progress = int(float(gs('progression_de_l_impression', '0'))) + except: progress = 0 + try: layer = int(float(gs('couche_actuelle', '0'))) + except: layer = 0 + try: layers_total = int(float(gs('nombre_total_de_couches', '0'))) + except: layers_total = 0 + result[str(p['id'])] = { + 'printer_name': p['name'], + 'status': gs('etat_de_l_impression'), + 'progress': progress, + 'task_name': gs('nom_de_la_tache'), + 'heure_fin': gs('heure_de_fin'), + 'layer': layer, + 'layers_total': layers_total, + 'error': gb('erreur_d_impression') == 'on', + } + return jsonify({'printers': result}) + except Exception as e: + return jsonify({'error': str(e), 'printers': {}}) + + # ─── Nextcloud WebDAV ──────────────────────────────────────────────────────── import xml.etree.ElementTree as ET @@ -2194,4 +2327,5 @@ def export_csv(): if __name__ == '__main__': init_db() + _start_ha_poll() app.run(host='0.0.0.0', port=5000, debug=False) diff --git a/templates/planning.html b/templates/planning.html index 67c7dc9..89915a0 100644 --- a/templates/planning.html +++ b/templates/planning.html @@ -92,6 +92,21 @@ + + +
+
+
+ Imprimantes en direct + +
+
+
+ Chargement… +
+
+
+
@@ -537,5 +552,65 @@ async function deleteSlot() { } initTimeline(); + +// ── Home Assistant live status ────────────────────────────────────────────── +async function refreshHaStatus() { + try { + const r = await fetch('/api/ha/status'); + const d = await r.json(); + const el = document.getElementById('haPrinterCards'); + const lu = document.getElementById('haLastUpdate'); + lu.textContent = 'Mis à jour ' + new Date().toLocaleTimeString('fr-FR', {hour:'2-digit',minute:'2-digit'}); + if (d.error && !d.printers) { + el.innerHTML = `
${d.error}
`; + return; + } + const printers = d.printers || {}; + const keys = Object.keys(printers); + if (!keys.length) { + el.innerHTML = '
Aucune imprimante avec préfixe HA. Configurez dans Paramètres + Imprimantes.
'; + return; + } + const STATUS_LABEL = {running:'En cours',finish:'Terminé',idle:'Idle',pause:'En pause',failed:'Erreur',offline:'Hors ligne',init:'Démarrage',unknown:'—'}; + const STATUS_CLS = {running:'success',finish:'secondary',idle:'light',pause:'warning',failed:'danger',offline:'dark',init:'info',unknown:'light'}; + el.innerHTML = keys.map(pid => { + const p = printers[pid]; + const sc = STATUS_CLS[p.status] || 'light'; + const sl = STATUS_LABEL[p.status] || p.status; + const prog = p.progress || 0; + const barColor = p.error ? '#ef4444' : p.status === 'running' ? '#22c55e' : '#94a3b8'; + const layerInfo = (p.layers_total > 0) + ? `Couche ${p.layer}/${p.layers_total}` + : ''; + const finInfo = (p.heure_fin && p.heure_fin !== 'unknown' && p.status === 'running') + ? `Fin ≈ ${new Date(p.heure_fin).toLocaleTimeString('fr-FR',{hour:'2-digit',minute:'2-digit'})}` + : ''; + const taskInfo = (p.task_name && p.task_name !== 'unknown') + ? `
${p.task_name}
` + : ''; + return `
+
+ ${p.printer_name} + ${sl} +
+ ${taskInfo} +
+
+
+
+
+
+ ${prog}% +
+
${layerInfo}${finInfo}
+
`; + }).join(''); + } catch(e) { + console.warn('HA status error:', e); + } +} +refreshHaStatus(); +setInterval(refreshHaStatus, 30000); + {% endblock %} diff --git a/templates/printers.html b/templates/printers.html index dfee709..273bd5b 100644 --- a/templates/printers.html +++ b/templates/printers.html @@ -15,7 +15,7 @@
- + {% for p in printers %} @@ -28,6 +28,13 @@ Inactive {% endif %} +
NomStatutNotes
NomStatutPréfixe HANotes
+ {% if p.ha_entity_prefix %} + {{ p.ha_entity_prefix }} + {% else %} + + {% endif %} + {{ p.notes or '—' }}
@@ -59,6 +66,12 @@
+
+ + +
Préfixe HA pour la synchro automatique du planning. Configurer l'URL HA dans Paramètres.
+
diff --git a/templates/settings.html b/templates/settings.html index 2d53b11..03a5422 100644 --- a/templates/settings.html +++ b/templates/settings.html @@ -239,6 +239,42 @@
+ +
+
+
Home Assistant — Sync imprimantes
+
+
+
+ + +
Sans slash final. Ex : https://homeassistant.lespatas.ovh
+
+
+ + +
Profil HA → Sécurité → Jetons d'accès longue durée.
+
+
+ + +
+ + Configurez ensuite le Préfixe entité HA pour chaque imprimante (ex : patabambulab01) dans la page Imprimantes. + Le planning se synchronise alors automatiquement toutes les 2 min. +
+
+
+
+
+
+
@@ -307,5 +343,22 @@ document.getElementById('testNcBtn').addEventListener('click', async function () res.innerHTML = `${e.message}`; } }); + +document.getElementById('testHaBtn').addEventListener('click', async function () { + const res = document.getElementById('testHaResult'); + res.textContent = 'Test en cours…'; + try { + const r = await fetch('/api/ha/status'); + const d = await r.json(); + if (d.error) { + res.innerHTML = `${d.error}`; + } else { + const count = Object.keys(d.printers).length; + res.innerHTML = `Connexion OK — ${count} imprimante(s) avec préfixe HA`; + } + } catch (e) { + res.innerHTML = `${e.message}`; + } +}); {% endblock %}