Pollice Verde: gestione dell'orto e del frutteto con irrigazione calcolata

App PWA che dice cosa fare nell'orto e, soprattutto, quanta acqua dare a ogni
coltura, calcolandolo sul meteo reale invece che su un calendario fisso.

Motore idrico (metodo FAO-56)
- il terreno come serbatoio: capacità dal tipo di suolo per la profondità
  radicale, prelievo giornaliero pari a ET0 × Kc, ricarica da pioggia efficace e
  irrigazioni al netto dell'efficienza del metodo
- bilancio ricostruito su 21 giorni reali e proiettato su 10 di previsione, così
  si sa anche fra quanti giorni servirà il prossimo turno e quando saltarlo
- soglia di intervento dipendente dal metodo: con la goccia si bagna al 55%
  della riserva utile, con l'aspersione all'85%, a mano si sfrutta tutto
- correzioni per pacciamatura, serra e coltura in vaso

Dati
- 58 schede fra ortaggi, frutta e aromatiche con coefficienti colturali,
  profondità radicali e fattori di deplezione da FAO-56, più calendario, sesti,
  consociazioni, cure e problemi tipici
- ricerca che capisce i nomi dialettali: cocomero, popone, petonciano, ramerino
- meteo ed evapotraspirazione da Open-Meteo, senza chiave API, con cache offline

Automazione
- generatore di programma per centraline a settori: ritmo di regime da impostare
  e giorni esatti con le piogge già scontate
- agente Node che custodisce lo stato, esegue i turni un settore alla volta e
  registra nel diario quello che ha davvero erogato
- driver per ESP32, Shelly Gen1/Gen2, Tasmota, più modalità simulata predefinita
- sonde Watermark: veto sui turni, riancoraggio del bilancio su misura reale,
  confronto fra sonda e calcolo
- sicurezze: tetti su minuti e litri, blocchi gelo/vento/pioggia, chiusura
  all'avvio e all'uscita, doppia chiusura a tempo lato dispositivo, un settore
  per volta, sospensione dei settori con sonda muta o guasta

Firmware ESP32 incluso, con schema di collegamento e istruzioni di posa.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GFnqupGt8T5HNNhPPHCjKs
This commit is contained in:
lucianoandClaude Opus 5 committed 2026-09-07 14:40:49 +02:00
commit b412d51658
46 files changed
+10608

No files matched your search

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

+7
View File
@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<rect width="512" height="512" rx="112" fill="#1f7a4d"/>
<path d="M256 400V214" stroke="#dcf0e4" stroke-width="26" stroke-linecap="round"/>
<path d="M256 246c0-58 44-104 104-112 6 62-38 112-104 112z" fill="#7ed6a4"/>
<path d="M256 300c0-52-40-94-94-101-5 56 34 101 94 101z" fill="#3fae7a"/>
<path d="M168 400h176" stroke="#e8a33d" stroke-width="26" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 454 B

+18
View File
@@ -0,0 +1,18 @@
{
"name": "Pollice Verde — orto e frutteto",
"short_name": "Pollice Verde",
"description": "Gestione dell'orto e del frutteto con piano di irrigazione calcolato sul meteo reale.",
"start_url": "/",
"scope": "/",
"display": "standalone",
"orientation": "portrait",
"background_color": "#f7f8f5",
"theme_color": "#1f7a4d",
"lang": "it",
"icons": [
{ "src": "/icon.svg", "sizes": "any", "type": "image/svg+xml", "purpose": "any" },
{ "src": "/icon-192.png", "sizes": "192x192", "type": "image/png", "purpose": "any" },
{ "src": "/icon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "any" },
{ "src": "/icon-maskable.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" }
]
}
+52
View File
@@ -0,0 +1,52 @@
/* Service worker minimale: l'app deve aprirsi anche in fondo all'orto senza campo.
- guscio dell'app: cache-first, aggiornata in sottofondo
- meteo: rete per prima, con l'ultima risposta buona come rete di sicurezza */
const CACHE = "pollice-verde-v1"
const GUSCIO = ["/", "/index.html", "/manifest.webmanifest", "/icon.svg"]
self.addEventListener("install", (e) => {
e.waitUntil(caches.open(CACHE).then((c) => c.addAll(GUSCIO)).then(() => self.skipWaiting()))
})
self.addEventListener("activate", (e) => {
e.waitUntil(
caches.keys().then((chiavi) => Promise.all(chiavi.filter((k) => k !== CACHE).map((k) => caches.delete(k))))
.then(() => self.clients.claim())
)
})
self.addEventListener("fetch", (e) => {
const req = e.request
if (req.method !== "GET") return
const url = new URL(req.url)
if (url.hostname.endsWith("open-meteo.com")) {
e.respondWith(
fetch(req)
.then((r) => {
const copia = r.clone()
caches.open(CACHE).then((c) => c.put(req, copia))
return r
})
.catch(() => caches.match(req))
)
return
}
if (url.origin !== location.origin) return
e.respondWith(
caches.match(req).then((colpo) => {
const rete = fetch(req)
.then((r) => {
if (r.ok) {
const copia = r.clone()
caches.open(CACHE).then((c) => c.put(req, copia))
}
return r
})
.catch(() => colpo || caches.match("/index.html"))
return colpo || rete
})
)
})