Compare commits
2 Commits
249999fa1e
...
b2e3867370
| Author | SHA1 | Date | |
|---|---|---|---|
| b2e3867370 | |||
| cab419bdc7 |
@@ -348,6 +348,69 @@ def _nessun_utente(db: Path) -> bool:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def camere_da_scaldare(db: Path) -> list[tuple[str, str]]:
|
||||||
|
"""Coppie (camera_url, camera_source) configurate nel db (stazioni e task di ricetta), senza doppioni.
|
||||||
|
Solo sorgenti "camera:" che aprono hardware (non "camera:folder"). Errore sqlite -> lista vuota (#108)."""
|
||||||
|
coppie: list[tuple[str, str]] = []
|
||||||
|
|
||||||
|
def aggiungi(url, src) -> None:
|
||||||
|
# #108: valida la coppia e scarta duplicati e sorgenti che non aprono hardware
|
||||||
|
if not (isinstance(url, str) and isinstance(src, str) and url.strip() and src.strip()):
|
||||||
|
return
|
||||||
|
if not src.startswith("camera:") or src == "camera:folder":
|
||||||
|
return
|
||||||
|
if (url, src) not in coppie:
|
||||||
|
coppie.append((url, src))
|
||||||
|
|
||||||
|
try:
|
||||||
|
con = sqlite3.connect(f"file:{db.as_posix()}?mode=ro", uri=True, timeout=2)
|
||||||
|
try:
|
||||||
|
for url, src in con.execute("SELECT camera_url, camera_source FROM stations"):
|
||||||
|
aggiungi(url, src)
|
||||||
|
for (blob,) in con.execute("SELECT acquisition_json FROM recipe_tasks WHERE acquisition_json IS NOT NULL"):
|
||||||
|
try:
|
||||||
|
cfg = json.loads(blob)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
continue # #108: JSON non valido, si salta
|
||||||
|
if isinstance(cfg, dict):
|
||||||
|
aggiungi(cfg.get("camera_url"), cfg.get("camera_source"))
|
||||||
|
finally:
|
||||||
|
con.close()
|
||||||
|
except Exception:
|
||||||
|
return []
|
||||||
|
return coppie
|
||||||
|
|
||||||
|
|
||||||
|
def scalda_camere(db: Path) -> None:
|
||||||
|
"""Apre ogni camera configurata con un grab a vuoto, cosi' il primo "Acquisisci" non aspetta ~25 s (#108).
|
||||||
|
Gira in un thread daemon: nessuna eccezione esce."""
|
||||||
|
try:
|
||||||
|
camere = camere_da_scaldare(db)
|
||||||
|
if not camere:
|
||||||
|
return
|
||||||
|
for _ in range(300): # aspetta il servizio camera (max ~60 s), come _apri_browser_quando_pronto
|
||||||
|
if _porta_aperta(PORTA_CAMERA):
|
||||||
|
break
|
||||||
|
time.sleep(0.2)
|
||||||
|
else:
|
||||||
|
log.warning("scalda camere: servizio camera non raggiungibile, salto")
|
||||||
|
return
|
||||||
|
for url, src in camere:
|
||||||
|
t0 = time.monotonic()
|
||||||
|
try:
|
||||||
|
richiesta = urllib.request.Request(
|
||||||
|
f"{url.rstrip('/')}/camera/grab",
|
||||||
|
data=json.dumps({"source": src, "trigger": "continuo", "timeout_ms": 5000}).encode("utf-8"),
|
||||||
|
headers={"Content-Type": "application/json"}, method="POST")
|
||||||
|
with urllib.request.urlopen(richiesta, timeout=90) as r:
|
||||||
|
esito = f"HTTP {r.status}"
|
||||||
|
log.info("scalda camera %s: %s in %.1f s", src, esito, time.monotonic() - t0)
|
||||||
|
except Exception as e: # #108: una camera che non risponde non ferma le altre
|
||||||
|
log.warning("scalda camera %s: %s dopo %.1f s", src, e, time.monotonic() - t0)
|
||||||
|
except Exception:
|
||||||
|
log.exception("scalda camere non riuscito")
|
||||||
|
|
||||||
|
|
||||||
def _apri_browser_quando_pronto(url: str, porte: tuple[int, ...]) -> None:
|
def _apri_browser_quando_pronto(url: str, porte: tuple[int, ...]) -> None:
|
||||||
# Aspetta che i server ascoltino (max ~60 s), poi apre il browser predefinito
|
# Aspetta che i server ascoltino (max ~60 s), poi apre il browser predefinito
|
||||||
for _ in range(300):
|
for _ in range(300):
|
||||||
@@ -509,6 +572,8 @@ def main(argv=None) -> int:
|
|||||||
|
|
||||||
registra_strumenti() # prima del server: il worker di visione legge registry() gia' popolato (#82)
|
registra_strumenti() # prima del server: il worker di visione legge registry() gia' popolato (#82)
|
||||||
thread = avvia_server(args.host, dati)
|
thread = avvia_server(args.host, dati)
|
||||||
|
# #108: apre le camere configurate con un grab a vuoto, con o senza browser
|
||||||
|
threading.Thread(target=scalda_camere, args=(dati / "tmf.db",), daemon=True).start()
|
||||||
if not args.senza_browser:
|
if not args.senza_browser:
|
||||||
# database senza utenti: si apre la pagina di setup del backend (SETUP_PASSWORD in config.env)
|
# database senza utenti: si apre la pagina di setup del backend (SETUP_PASSWORD in config.env)
|
||||||
url = (f"http://127.0.0.1:{PORTA_BACKEND}/api/setup" if _nessun_utente(dati / "tmf.db")
|
url = (f"http://127.0.0.1:{PORTA_BACKEND}/api/setup" if _nessun_utente(dati / "tmf.db")
|
||||||
|
|||||||
@@ -271,3 +271,77 @@ def test_avvio_normale_registra_gli_strumenti_prima_del_server(avvio, tmp_path,
|
|||||||
assert avvio.main(["--senza-finestre", "--senza-browser"]) == 0
|
assert avvio.main(["--senza-finestre", "--senza-browser"]) == 0
|
||||||
dati = tmp_path / "Tielogic" / "TieMeasureFlow"
|
dati = tmp_path / "Tielogic" / "TieMeasureFlow"
|
||||||
assert avvio.chiamate == ["migrazioni", "strumenti", ("127.0.0.1", dati)]
|
assert avvio.chiamate == ["migrazioni", "strumenti", ("127.0.0.1", dati)]
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------- scalda camere (#108)
|
||||||
|
|
||||||
|
def _db_camere(percorso):
|
||||||
|
# #108: db minimo con le due tabelle lette da camere_da_scaldare
|
||||||
|
con = sqlite3.connect(percorso)
|
||||||
|
con.execute("CREATE TABLE stations (id INTEGER PRIMARY KEY, camera_url TEXT, camera_source TEXT)")
|
||||||
|
con.execute("CREATE TABLE recipe_tasks (id INTEGER PRIMARY KEY, acquisition_json TEXT)")
|
||||||
|
con.executemany("INSERT INTO stations (camera_url, camera_source) VALUES (?, ?)", [
|
||||||
|
("http://127.0.0.1:8765", "camera:601020"), # valida
|
||||||
|
(None, None), # stazione senza camera
|
||||||
|
("http://127.0.0.1:8765", "camera:folder"), # non apre hardware
|
||||||
|
])
|
||||||
|
con.executemany("INSERT INTO recipe_tasks (acquisition_json) VALUES (?)", [
|
||||||
|
(json.dumps({"camera_url": "http://127.0.0.1:8765", "camera_source": "camera:601020"}),), # duplicato
|
||||||
|
(json.dumps({"camera_url": "http://10.0.0.5:8765/", "camera_source": "camera:777"}),), # valida
|
||||||
|
("{rotto",), # JSON non valido
|
||||||
|
(None,),
|
||||||
|
])
|
||||||
|
con.commit()
|
||||||
|
con.close()
|
||||||
|
|
||||||
|
|
||||||
|
def test_camere_da_scaldare_filtra_e_deduplica(avvio, tmp_path):
|
||||||
|
db = tmp_path / "tmf.db"
|
||||||
|
_db_camere(db)
|
||||||
|
assert avvio.camere_da_scaldare(db) == [
|
||||||
|
("http://127.0.0.1:8765", "camera:601020"),
|
||||||
|
("http://10.0.0.5:8765/", "camera:777"),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_camere_da_scaldare_db_inesistente(avvio, tmp_path):
|
||||||
|
assert avvio.camere_da_scaldare(tmp_path / "non_esiste.db") == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_scalda_camere_una_chiamata_per_coppia(avvio, tmp_path, monkeypatch):
|
||||||
|
db = tmp_path / "tmf.db"
|
||||||
|
_db_camere(db)
|
||||||
|
monkeypatch.setattr(avvio, "_porta_aperta", lambda porta: True)
|
||||||
|
chiamate = []
|
||||||
|
|
||||||
|
class Risposta:
|
||||||
|
status = 200
|
||||||
|
def __enter__(self): return self
|
||||||
|
def __exit__(self, *a): return False
|
||||||
|
|
||||||
|
def finto_urlopen(richiesta, timeout=None):
|
||||||
|
chiamate.append((richiesta.full_url, json.loads(richiesta.data), richiesta.get_method(), timeout))
|
||||||
|
return Risposta()
|
||||||
|
|
||||||
|
monkeypatch.setattr(avvio.urllib.request, "urlopen", finto_urlopen)
|
||||||
|
avvio.scalda_camere(db)
|
||||||
|
corpo = {"trigger": "continuo", "timeout_ms": 5000}
|
||||||
|
assert chiamate == [
|
||||||
|
("http://127.0.0.1:8765/camera/grab", {"source": "camera:601020", **corpo}, "POST", 90),
|
||||||
|
("http://10.0.0.5:8765/camera/grab", {"source": "camera:777", **corpo}, "POST", 90),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_scalda_camere_eccezione_urlopen_non_esce(avvio, tmp_path, monkeypatch):
|
||||||
|
db = tmp_path / "tmf.db"
|
||||||
|
_db_camere(db)
|
||||||
|
monkeypatch.setattr(avvio, "_porta_aperta", lambda porta: True)
|
||||||
|
tentativi = []
|
||||||
|
|
||||||
|
def urlopen_rotto(richiesta, timeout=None):
|
||||||
|
tentativi.append(richiesta.full_url)
|
||||||
|
raise OSError("camera irraggiungibile")
|
||||||
|
|
||||||
|
monkeypatch.setattr(avvio.urllib.request, "urlopen", urlopen_rotto)
|
||||||
|
avvio.scalda_camere(db) # non solleva e passa alla camera successiva
|
||||||
|
assert len(tentativi) == 2
|
||||||
|
|||||||
Reference in New Issue
Block a user