workspace: Jeep auto-identifiee, photo de tableau de bord, releves km, transfert Stoik

- concierge-live.js : variante (a), la JEEP identifiee par Uconnect recoit une
  ligne d'info sans question ; attente de 15 min avant de questionner une voiture
  inconnue ; releves km ; reponse sur une ligne ; simulation sans ecriture d'etat.
- concierge-write.js (EVCS) : niveau et releve km, tests.
- lecture-tableau.py : lecture d'une photo de tableau de bord par la Spark,
  confirmation avant envoi au concierge, % ignore si branchee depuis > 10 min.
- stoik-transfert.py : rapports Stoik de la boite AIRTRIA vers la Gmail, une fois.
- AGENTS.md et reference-concierge.md alignes.
- .gitignore : deux scripts ajoutes, DREAMS de l'agent bilans exclu.
This commit is contained in:
2026-10-04 15:50:25 +02:00
parent b6dccc0481
commit 4cd3615172
11 changed files with 564 additions and 40 deletions

View File

@@ -0,0 +1,77 @@
#!/usr/bin/env python3
# stoik-transfert.py — fait suivre vers la Gmail de Thierry chaque rapport Stoïk (protect@stoik.io)
# reçu sur la boîte AIRTRIA avec un PDF joint, UNE SEULE FOIS par mail (03/10/2026, « GO transfert Stoïk »).
# Motif : ATELIER_OP (autre site, sans chemin réseau vers spark) récupère les PDF via Gmail pour son
# bilan sécurité. Lancé par le cron airtria-tri-quotidien (19:50), après airtria-menage.py.
# python3 stoik-transfert.py → transfère les nouveaux rapports
# python3 stoik-transfert.py --essai → liste ce qui serait envoyé, n'envoie rien, n'écrit pas l'état
# python3 stoik-transfert.py --amorcer → marque les rapports déjà présents comme transférés (mise en service)
# Lecture IMAP seule (BODY.PEEK, sélection readonly) : rien n'est marqué lu, déplacé ni supprimé.
# État : stoik-transfert-state.json (Message-ID déjà transférés). Secrets dans /home/node/.openclaw/.env.
import imaplib, ssl, socket, email, email.header, email.utils, json, os, sys, subprocess, tempfile, shutil
WS = "/home/node/.openclaw/workspace"
STATE = os.path.join(WS, "stoik-transfert-state.json")
GMAIL = "tallagrand.thierry@gmail.com"
ESSAI = "--essai" in sys.argv
AMORCE = "--amorcer" in sys.argv # marque les rapports existants comme déjà transférés, sans envoyer
env = dict(l.strip().split("=", 1) for l in open("/home/node/.openclaw/.env") if "=" in l and not l.startswith("#"))
PWD = env["AIRTRIA_IMAP_PASSWORD"].strip().strip('"').strip("'")
class _IMAPSurSocket(imaplib.IMAP4):
def __init__(self, sock): self._s = sock; imaplib.IMAP4.__init__(self, "", 993)
def open(self, host, port, timeout=None): self.host, self.port, self.sock = host, port, self._s; self.file = self.sock.makefile("rb")
def dec(v):
try: return str(email.header.make_header(email.header.decode_header(v or "")))
except Exception: return str(v or "")
deja = set(json.load(open(STATE))) if os.path.exists(STATE) else set()
ctx = ssl.create_default_context() # certificat vérifié sur le nom SNI
m = _IMAPSurSocket(ctx.wrap_socket(socket.create_connection(("10.10.10.22", 993), 120),
server_hostname="airtria-dsm2.direct.quickconnect.to"))
m.login("TMZF5455", PWD)
m.select("INBOX", readonly=True)
typ, d = m.uid("search", None, 'FROM "stoik.io"')
a_envoyer = []
for u in d[0].split():
typ, r = m.uid("fetch", u, "(BODY.PEEK[])")
msg = email.message_from_bytes(r[0][1])
mid = (msg.get("Message-ID") or f"uid-{u.decode()}").strip()
if mid in deja: continue
pdfs = [(dec(p.get_filename()), p.get_payload(decode=True)) for p in msg.walk()
if p.get_filename() and dec(p.get_filename()).lower().endswith(".pdf")]
if not pdfs: continue
a_envoyer.append((mid, email.utils.parsedate_to_datetime(msg["Date"]), dec(msg["Subject"]), pdfs))
m.logout()
if AMORCE:
deja.update(mid for mid, *_ in a_envoyer); json.dump(sorted(deja), open(STATE, "w"), indent=1)
print(f"stoik-transfert : {len(a_envoyer)} rapport(s) existant(s) marqué(s) comme déjà transférés, rien envoyé."); sys.exit(0)
if not a_envoyer:
print("stoik-transfert : aucun nouveau rapport Stoïk avec PDF."); sys.exit(0)
envoyes = 0
for mid, dt, sujet, pdfs in a_envoyer:
noms = [f"{dt.strftime('%Y-%m-%d')}_{n}" for n, _ in pdfs]
if ESSAI:
print(f"[ESSAI] enverrait : {dt.strftime('%d/%m/%Y')} | {sujet} | {', '.join(noms)}"); continue
tmp = tempfile.mkdtemp(prefix="stoik-")
try:
att = []
for nom, (_, data) in zip(noms, pdfs):
chemin = os.path.join(tmp, nom); open(chemin, "wb").write(data); att.append(f"--attach={chemin}")
corps = (f"Rapport Stoïk reçu le {dt.strftime('%d/%m/%Y')} sur la boîte airtria.fr (protect@stoik.io), "
f"transféré automatiquement par le tri du soir.\n\nÀ enregistrer sur le poste d'ATELIER_OP dans "
f"~/Documents/SYNERIC/securite/stoik/ pour son bilan sécurité.")
res = subprocess.run(["gog", "gmail", "send", "-a", GMAIL, "--to", GMAIL,
"--subject", f"[Stoïk → ATELIER_OP] {sujet} ({dt.strftime('%d/%m/%Y')})",
"--body", corps] + att, capture_output=True, text=True, timeout=180)
if res.returncode != 0:
print(f"stoik-transfert : ÉCHEC d'envoi pour « {sujet} » : {res.stderr.strip()[:300]}"); continue
deja.add(mid); envoyes += 1
json.dump(sorted(deja), open(STATE, "w"), indent=1)
print(f"stoik-transfert : envoyé « {sujet} » du {dt.strftime('%d/%m/%Y')} ({len(att)} PDF)")
finally:
shutil.rmtree(tmp, ignore_errors=True)
if not ESSAI: print(f"stoik-transfert : {envoyes} rapport(s) transféré(s).")