Automatizace vertikálního zemědělství: Robotické ovládání přes API
Opuštěný průmyslový sklad v Cavenago di Brianza, na okraji Milána. Uvnitř 9000 metrů čtverce vertikálních regálů, kde pod modro-červenými LED světly rostou saláty, bazalka a roketa aniž by kdy viděl slunce. Konstantní teplota 21 stupňů, vlhkost 70 %, CO2 1 200 ppm. Každá rostlina má personalizovanou světelnou recepturu, každá kultivační linka komunikuje s centrálním systémem přes API, každý robot ví, kam jít v reálném čase. Tohle je Planetární farmy, italská vertikální farma který se v roce 2025 stal jednou ze světových referencí pro automatizované indoor zemědělství.
Vertikální zemědělství již není laboratorním experimentem. V roce 2025 stojí globální trh 9,62 miliardy dolarů s projekcemi 39,2 miliard do roku 2033 (CAGR 19,3 %). Vertikální farmy nové generace spotřebovávají O 95 % méně vody respekt k tradičnímu zemědělství, produkují po celý rok bez ohledu na klima, eliminují pesticidy a dosahují až 100krát vyšších výnosů na metr čtvereční než na volném poli. Ale tyto výsledky jsou možné pouze s průmyslovým softwarem a robotickou infrastrukturou.
V tomto článku budujeme celou softwarovou architekturu vertikální farmy: z environmentálních senzorů na PID regulátor v Pythonu, od návrhu REST API s FastAPI po integraci ROS2 pro roboty, od digitálního dvojčete po posílení optimalizačního kanálu založeného na učení. pracovní kód, architektury testované ve výrobě, reálná čísla z italského ekosystému.
Co se dozvíte v tomto článku
- Kompletní softwarová architektura pro vertikální farmy: senzory, ovladače, SCADA, cloud, AI
- Správa spektra LED: PAR, DLI, světelné receptury pro hlávkový salát, bazalku a jahody
- Implementace PID regulátoru v Pythonu pro teplotu, CO2, vlhkost a zavlažování
- Návrh REST API s FastAPI pro správu receptury plodin a ovládání pohonu
- Integrace ROS2 pro automatizované robotické setí, přesazování a sklizeň
- Digitální dvojče farmy: simulace růstu rostlin a optimalizace uspořádání
- Posílení učení pro optimalizaci lehkých receptur
- Infrastruktura IoT: Modbus RTU, MQTT, OPC-UA, průmyslové brány
- Ekonomická analýza: CAPEX, OPEX, break-even a srovnání s venkovním hospodařením
- Případová studie: Planet Farms a Agricola Moderna, přední italské vertikální farmy
FoodTech Series – všechny články
| # | Položka | Úroveň | Stát |
|---|---|---|---|
| 1 | IoT Pipeline pro přesné zemědělství s Pythonem a MQTT | Moderní | Publikováno |
| 2 | ML Edge pro monitorování plodin: Počítačové vidění v polích | Moderní | Publikováno |
| 3 | Satelitní API a vegetační indexy: NDVI s Pythonem a Sentinel-2 | Střední | Publikováno |
| 4 | Sledovatelnost blockchainu v potravinách: od pole až po supermarket | Střední | Publikováno |
| 5 | Počítačová vize pro kontrolu kvality v potravinářském průmyslu | Moderní | Publikováno |
| 6 | FSMA a Digital Compliance: Automatizace regulačních procesů | Střední | Publikováno |
| 7 | Vertical Farming Automation: Robotické ovládání přes API (jste zde) | Moderní | Proud |
| 8 | Prognóza poptávky pro maloobchod s potravinami s Prophet a LightGBM | Střední | Již brzy |
| 9 | Farm Intelligence Dashboard: Analýza v reálném čase s Grafanou | Střední | Již brzy |
| 10 | Optimalizace potravin v dodavatelském řetězci: ML pro snížení odpadu | Střední | Již brzy |
Vertikální zemědělský trh v roce 2025: růst a technologické hnací síly
Vertikální zemědělství prošlo v letech 2023–2024 fází konsolidace s některými velkými hráči Severoamerické společnosti (AeroFarms, AppHarvest, Bowery Farming), které pod břemenem vyhlásily bankrot velmi vysokých CAPEX a explodujících nákladů na energii. Ale trh se nezastavil: byl restrukturalizován kolem efektivnějších modelů, s darwinovským výběrem, který odměňoval ty, kteří stavěli ekonomika pevných jednotek před škálováním.
V roce 2025 čísla vyprávějí příběh rostoucí vyspělosti: globální trh za to stojí 9,62 miliardy dolarů a do roku 2033 vzroste na 39,2 miliardy. Další odhad (Maximize Market Research) ji v roce 2025 uvádí na 8 miliard s projekcemi 39,7 miliard do 2032 při CAGR 25,7 %. Rozptyl v odhadech odráží obtížnost klasifikace toho, co tvoří přesně "vertikální farmaření" (pouze vnitřní skládané podnosy? také pokročilé skleníky?), ale trend a jednoznačné. Propojený trh zemědělské robotiky má v roce 2025 hodnotu 10,23 miliardy růst na 28,2 miliardy do roku 2030.
Vertikální zemědělství vs tradiční zemědělství: Porovnání výkonnosti
| Parametr | Venkovní zemědělství | Skleník | Vertikální farma |
|---|---|---|---|
| Spotřeba vody (relativní) | 100 % | 30–40 % | 5–10 % |
| Výnos na m² (hlávkový salát) | ~2 kg/m²/rok | ~15 kg/m²/rok | ~150-200 kg/m²/rok |
| Výrobní cykly/rok | 1-3 | 4-8 | 12-18 |
| Pesticidy | Vysoká nutnost | Snížená | Nula |
| Závislost na klimatu | Celkový | Částečný | Žádný |
| Využití půdy (půda) | 1x | 1x | 0,01-0,05x |
| Potřebná energie | Nízký | Průměrný | Vysoká (LED + HVAC) |
| Výrobní náklady (hlávkový salát) | 0,5-1 USD/kg | 1,5-3 $/kg | 4-8 $/kg |
V roce 2025 jsou čtyři hnací síly růstu: za prvé, náklady na vysoce účinné LED diody klesly o 70 % za poslední desetiletí, díky čemuž jsou náklady na osvětlení mnohem dostupnější. Za druhé, tj Řídicí systémy založené na AI umožňují nemyslitelné energetické optimalizace po dobu až 3–4 let před. Za třetí, poptávka po místních, čerstvých produktech bez pesticidů neustále roste, zejména v městských oblastech. Za čtvrté, robotické moduly pro setí, přesazování a sklizeň jsou nyní dostupné za průmyslové ceny, již nejen laboratorní.
Softwarová architektura pro Vertical Farm: The Complete Stack
Moderní vertikální farma je v podstatě kyber-fyzický systém: každé fyzické rozhodnutí (zapnout LED, otevření ventilu, pohyb robota) a výsledek softwarového výpočtu. Architektura musí být v reálném čase pro kontrolu, spolehlivý pro bezpečnost plodin a škálovatelný spravovat stovky pěstebních ploch.
End-to-End architektonický zásobník
+-----------------------------------------------------------------------+
| LAYER 1: FIELD DEVICES |
| [Sensori Temp/RH] [CO2 Sensor] [PAR Meter] [Nutrient EC/pH] |
| [Flow Sensor] [Camera RGB-D] [Weight Sensor] [RFID Tray] |
| | | | | |
| +------------------+-------------+--------------+ |
| Modbus RTU / RS-485 / I2C / SPI |
+-----------------------------------------------------------------------+
|
+-----------------------------------------------------------------------+
| LAYER 2: EDGE CONTROLLER |
| [PLC Siemens S7-1500 / Beckhoff CX / Raspberry] |
| - Loop PID per temperatura, CO2, umidita |
| - Scheduling ricette luminose (LED driver DMX/PWM) |
| - Gestione irrigazione NFT/DWC cicli |
| - Buffer locale offline-tolerant |
| - OPC-UA Server / MQTT Publisher |
+-----------------------------------------------------------------------+
|
+-----------------------------------------------------------------------+
| LAYER 3: SCADA / MES |
| [Ignition SCADA / custom Python SCADA] |
| - Supervisione multi-zona real-time |
| - Storicizzazione time-series (InfluxDB/TimescaleDB) |
| - Allarmi e notifiche (temperatura, EC, pH out-of-range) |
| - Ricette colturali e scheduling batch |
+-----------------------------------------------------------------------+
|
+-----------------------------------------------------------------------+
| LAYER 4: CLOUD PLATFORM |
| [FastAPI Backend] [Message Broker MQTT/Kafka] [PostgreSQL] |
| - REST API per integrazione ERP/WMS/retail |
| - Gestione ricette, batch, inventory, ordini |
| - Autenticazione OAuth2, RBAC, audit log |
+-----------------------------------------------------------------------+
|
+-----------------------------------------------------------------------+
| LAYER 5: AI / ANALYTICS |
| [ML Pipeline] [Digital Twin] [Computer Vision] [RL Optimizer] |
| - Ottimizzazione ricette luminose (RL) |
| - Previsione resa e time-to-harvest |
| - Rilevamento anomalie (sensori + visione) |
| - Simulazione crescita (digital twin) |
+-----------------------------------------------------------------------+
Volba mezi průmyslovým PLC (Siemens S7, Beckhoff) a jednodeskovým počítačem (Raspberry Pi 4, BeagleBone) závisí na požadované úrovni spolehlivosti. Pro komerční farmy s vysokou úrodou hodnotu, PLC s certifikací IEC 61131-3 s hardwarovou redundancí a správnou volbou. Pro prototypy a experimentální farmy, řešení Python na vestavěném hardwaru a flexibilnější a rychlejší na vývoj.
Systémy kontroly prostředí: LED, HVAC, CO2 a zavlažování
Kontrola prostředí je srdcem provozu vertikální farmy. Dominují čtyři parametry: světelné spektrum a intenzita, teplota vzduchu, koncentrace CO2 a složení živného roztoku. Každý z nich vyžaduje vyhrazenou řídicí smyčku.
LED Spectrum Management: PAR, DLI a Light Recipes
Rostliny nevyužívají veškeré viditelné světlo stejně. Fotosynteticky aktivní rozsah jde od 400 až 700 nm (PAR - Fotosynteticky aktivní záření). Uvnitř V tomto rozmezí modrá (400-500 nm) reguluje morfologii listů a syntézu sloučenin aromatické látky; červená (600-700 nm) je hlavní hnací silou fotosyntézy; daleko červená (700-800 nm) ovlivňuje kvetení a geometrii rostlin prostřednictvím fytochromového systému.
DLI (Daily Light Integral) měří celkové množství fotonů PAR, které rostlina přijme za 24 hodin, vyjádřeno v mol/m²/den. A nejdůležitější metrika pro dimenzování receptů světlý. Výzkum publikovaný v Nature Scientific Reports v roce 2025 ukazuje, že optimalizované LED pro vertikální hospodaření produkují až o 32 % vyšší výnos ve srovnání se spektry standardní, s vyšší čerstvou hmotností v salátu a bazalce.
Světelné parametry pro hlavní plodiny
| Kultura | PPFD (µmol/m²/s) | Cíl DLI (mol/m²/den) | Optimální spektrum | Fotoperioda |
|---|---|---|---|---|
| Salát (list) | 150-250 | 15-18 hodin | R:B = 4:1, + daleko červená 5 % | 16-18h světlo |
| Salát (hlava) | 200-300 | 17-22 | R:B = 3:1, UV 380nm 2% | 16h světla |
| Bazalka | 200-300 | 14–17 hodin | R:B = 3:1, modrá 20-25% | 16h světla |
| Špenát | 150-200 | 12-17 | R:B = 4:1 | 14-16h světlo |
| Jahody (vegetativní) | 200-300 | 15-20 hodin | R:B = 3:1 | 16h světla |
| Jahody (kvetoucí) | 300-400 | 20-25 | R:B:FR = 3:1:0,5 | 12h světla |
| Microgreens | 100-200 | 8-12 | Celospektrální bílá | 16h světla |
| Aromatické bylinky | 200-250 | 14-16 | Modrá 15 %, R dominantní | 16h světla |
Řídicí jednotka LED musí tyto receptury převést na signály PWM do ovladačů LED. Každá pěstitelská oblast může mít jinou recepturu a receptura se může v průběhu roku měnit kultivační cyklus (např. více modré za posledních 48 hodin pro zesílení aroma bazalky). Zde je implementace Pythonu kompletního PID regulátoru:
"""
Vertical Farm Environmental Controller
Controller PID per gestione LED, CO2, temperatura e irrigazione
"""
import time
import asyncio
import logging
from dataclasses import dataclass, field
from typing import Optional
from enum import Enum
logger = logging.getLogger(__name__)
class CropStage(Enum):
GERMINATION = "germination"
SEEDLING = "seedling"
VEGETATIVE = "vegetative"
MATURATION = "maturation"
HARVEST_READY = "harvest_ready"
@dataclass
class LightRecipe:
"""Ricetta luminosa per una coltura in un determinato stadio"""
crop_name: str
stage: CropStage
ppfd_target: float # µmol/m²/s
dli_target: float # mol/m²/giorno
photoperiod_hours: float # ore di luce al giorno
spectrum_red_pct: float # % canale rosso (620-700nm)
spectrum_blue_pct: float # % canale blu (400-500nm)
spectrum_white_pct: float # % LED bianco full-spectrum
spectrum_farred_pct: float # % far-red (700-800nm)
spectrum_uv_pct: float = 0.0
def validate(self) -> bool:
total = (self.spectrum_red_pct + self.spectrum_blue_pct +
self.spectrum_white_pct + self.spectrum_farred_pct +
self.spectrum_uv_pct)
return abs(total - 100.0) < 0.1
def ppfd_from_dli(self) -> float:
"""Calcola PPFD target dalle ore di fotoperiodo e DLI"""
photoperiod_seconds = self.photoperiod_hours * 3600
return (self.dli_target * 1_000_000) / photoperiod_seconds
@dataclass
class PIDController:
"""
Controller PID generico per parametri ambientali.
Usa anti-windup per prevenire integrator saturation.
"""
kp: float
ki: float
kd: float
setpoint: float
output_min: float = 0.0
output_max: float = 100.0
_integral: float = field(default=0.0, init=False)
_last_error: float = field(default=0.0, init=False)
_last_time: float = field(default_factory=time.time, init=False)
def compute(self, measured_value: float) -> float:
current_time = time.time()
dt = current_time - self._last_time
if dt <= 0:
return self._last_output if hasattr(self, '_last_output') else 0.0
error = self.setpoint - measured_value
# Proporzionale
p_term = self.kp * error
# Integrale con anti-windup (clamping)
self._integral += error * dt
i_term = self.ki * self._integral
# Clamp integrale per prevenire windup
i_max = (self.output_max - self.output_min) / self.ki if self.ki != 0 else 1000
self._integral = max(-i_max, min(i_max, self._integral))
i_term = self.ki * self._integral
# Derivativo
d_term = self.kd * (error - self._last_error) / dt if dt > 0 else 0.0
output = p_term + i_term + d_term
output = max(self.output_min, min(self.output_max, output))
self._last_error = error
self._last_time = current_time
self._last_output = output
return output
class EnvironmentalController:
"""
Controller principale per una zona di coltivazione.
Gestisce temperatura, CO2, umidita e irrigazione via PID.
"""
def __init__(self, zone_id: str, recipe: LightRecipe):
self.zone_id = zone_id
self.recipe = recipe
# PID temperatura: setpoint 21°C, banda ±1°C
self.temp_pid = PIDController(
kp=2.0, ki=0.5, kd=0.1,
setpoint=21.0,
output_min=-100.0, # raffreddamento
output_max=100.0 # riscaldamento
)
# PID CO2: setpoint 1200 ppm per crescita accelerata
self.co2_pid = PIDController(
kp=0.5, ki=0.1, kd=0.05,
setpoint=1200.0,
output_min=0.0,
output_max=100.0 # % apertura valvola CO2
)
# PID umidita relativa: setpoint 70%
self.humidity_pid = PIDController(
kp=1.5, ki=0.3, kd=0.05,
setpoint=70.0,
output_min=0.0,
output_max=100.0
)
def compute_led_pwm(self, current_hour: float) -> dict:
"""
Calcola duty cycle PWM per ogni canale LED
in base all'ora del giorno e alla ricetta.
"""
# Determina se siamo nel fotoperiodo attivo
# Fotoperiodo: ore 6:00 - (6 + photoperiod_hours)
start_hour = 6.0
end_hour = start_hour + self.recipe.photoperiod_hours
if not (start_hour <= current_hour < end_hour):
return {
'red': 0.0, 'blue': 0.0,
'white': 0.0, 'farred': 0.0, 'uv': 0.0
}
# Calcola intensità normalizzata (0-1) dal PPFD target
# Assumendo che 100% PWM = 600 µmol/m²/s
max_ppfd = 600.0
intensity = min(self.recipe.ppfd_target / max_ppfd, 1.0)
return {
'red': round(intensity * self.recipe.spectrum_red_pct / 100, 4),
'blue': round(intensity * self.recipe.spectrum_blue_pct / 100, 4),
'white': round(intensity * self.recipe.spectrum_white_pct / 100, 4),
'farred': round(intensity * self.recipe.spectrum_farred_pct / 100, 4),
'uv': round(intensity * self.recipe.spectrum_uv_pct / 100, 4),
}
async def control_loop(self, sensor_reader, actuator_writer, interval: float = 30.0):
"""
Loop di controllo asincrono: legge sensori, calcola PID, scrive attuatori.
Frequenza default: ogni 30 secondi.
"""
logger.info(f"Avvio loop controllo zona {self.zone_id}")
while True:
try:
# Leggi sensori
sensors = await sensor_reader.read_zone(self.zone_id)
# Calcola output PID
temp_output = self.temp_pid.compute(sensors['temperature'])
co2_output = self.co2_pid.compute(sensors['co2_ppm'])
humidity_output = self.humidity_pid.compute(sensors['humidity_rh'])
# Calcola PWM LED
from datetime import datetime
current_hour = datetime.now().hour + datetime.now().minute / 60.0
led_pwm = self.compute_led_pwm(current_hour)
# Scrivi attuatori
await actuator_writer.set_hvac(self.zone_id, temp_output, humidity_output)
await actuator_writer.set_co2_valve(self.zone_id, co2_output)
await actuator_writer.set_led_channels(self.zone_id, led_pwm)
logger.debug(
f"Zona {self.zone_id} | "
f"T={sensors['temperature']:.1f}°C (PID:{temp_output:.1f}%) | "
f"CO2={sensors['co2_ppm']:.0f}ppm (valve:{co2_output:.1f}%) | "
f"RH={sensors['humidity_rh']:.1f}% | "
f"LED R:{led_pwm['red']:.2f} B:{led_pwm['blue']:.2f}"
)
except Exception as e:
logger.error(f"Errore loop controllo zona {self.zone_id}: {e}")
await asyncio.sleep(interval)
# --- Ricette standard per colture comuni ---
LETTUCE_VEGETATIVE = LightRecipe(
crop_name="Lattuga Lollo",
stage=CropStage.VEGETATIVE,
ppfd_target=200.0,
dli_target=17.0,
photoperiod_hours=16.0,
spectrum_red_pct=65.0,
spectrum_blue_pct=20.0,
spectrum_white_pct=10.0,
spectrum_farred_pct=5.0,
spectrum_uv_pct=0.0,
)
BASIL_VEGETATIVE = LightRecipe(
crop_name="Basilico Genovese",
stage=CropStage.VEGETATIVE,
ppfd_target=250.0,
dli_target=15.0,
photoperiod_hours=16.0,
spectrum_red_pct=60.0,
spectrum_blue_pct=25.0, # blue elevato per aromi
spectrum_white_pct=10.0,
spectrum_farred_pct=5.0,
spectrum_uv_pct=0.0,
)
STRAWBERRY_FLOWERING = LightRecipe(
crop_name="Fragola Elsanta",
stage=CropStage.MATURATION,
ppfd_target=350.0,
dli_target=22.0,
photoperiod_hours=12.0, # fotoperiodo corto per fioritura
spectrum_red_pct=55.0,
spectrum_blue_pct=20.0,
spectrum_white_pct=20.0,
spectrum_farred_pct=5.0,
spectrum_uv_pct=0.0,
)
Zavlažovací systémy: NFT, DWC a aeroponie
Tři dominantní hydroponické technologie ve vertikálních farmách mají řídicí architekturu různé, každý má své vlastní kritické parametry pro monitorování a regulaci.
Srovnání hydroponických systémů
| Systém | Ideální plodiny | Kritické parametry | Kontrola složitosti | Spotřeba vody |
|---|---|---|---|---|
| NFT (Technika výživného filmu) | Saláty, bylinky | Průtok, sklon kanálu, EC, pH | Průměrný | Minimum (recirkulace) |
| DWC (Deep Water Culture) | Saláty, špenát | Okysličení (DO), EC, pH, teplota | Nízký | Bas |
| Aeroponie | Jahody, kořeny, bylinky | Nebulizační cyklus, tlak, EC, pH | Vysoký | Minimum (90 % vs. půda) |
| Substráty (kokos/rockwool) | Rajčata, paprika | Cyklické zavlažování, EC, pH, drenáž | Průměrný | Mírný |
"""
Irrigation Controller per sistema NFT
Gestione pompe, monitoraggio EC/pH, dosaggio nutrienti
"""
import asyncio
from dataclasses import dataclass
from typing import Optional
import logging
logger = logging.getLogger(__name__)
NUTRIENT_TARGETS = {
"lettuce": {"ec_ms_cm": 1.6, "ph": 6.0, "temp_c": 20.0},
"basil": {"ec_ms_cm": 1.8, "ph": 6.0, "temp_c": 21.0},
"spinach": {"ec_ms_cm": 2.0, "ph": 6.2, "temp_c": 20.0},
"strawberry": {"ec_ms_cm": 2.2, "ph": 5.8, "temp_c": 18.0},
"herbs": {"ec_ms_cm": 1.4, "ph": 6.0, "temp_c": 21.0},
}
@dataclass
class NFTController:
zone_id: str
crop_type: str
flow_rate_lpm: float = 1.5 # litri/minuto per canale
channel_slope_pct: float = 2.0 # pendenza canale in %
def get_targets(self) -> dict:
return NUTRIENT_TARGETS.get(self.crop_type, NUTRIENT_TARGETS["lettuce"])
async def check_and_adjust(self, ec_sensor: float, ph_sensor: float,
ec_doser, ph_doser) -> dict:
targets = self.get_targets()
actions = {}
# Controllo EC
ec_delta = targets["ec_ms_cm"] - ec_sensor
if abs(ec_delta) > 0.2:
if ec_delta > 0:
# EC troppo bassa: aggiungi concentrato nutrienti
dose_ml = ec_delta * 50 # ml di concentrato A+B
await ec_doser.dose(zone=self.zone_id, ml=dose_ml, solution="AB")
actions["ec_dosing"] = f"+{dose_ml:.1f}ml AB"
else:
# EC troppo alta: diluisci con acqua RO
await ec_doser.dose_water(zone=self.zone_id, ml=abs(ec_delta) * 100)
actions["ec_dilution"] = f"+{abs(ec_delta)*100:.0f}ml H2O"
# Controllo pH
ph_delta = targets["ph"] - ph_sensor
if abs(ph_delta) > 0.3:
if ph_delta > 0:
# pH troppo basso: aggiungi pH-up (KOH)
dose_ml = abs(ph_delta) * 10
await ph_doser.dose(zone=self.zone_id, ml=dose_ml, solution="ph_up")
actions["ph_adjust"] = f"pH-up +{dose_ml:.1f}ml"
else:
# pH troppo alto: aggiungi pH-down (H3PO4)
dose_ml = abs(ph_delta) * 10
await ph_doser.dose(zone=self.zone_id, ml=dose_ml, solution="ph_down")
actions["ph_adjust"] = f"pH-down +{dose_ml:.1f}ml"
logger.info(
f"NFT zona {self.zone_id} | "
f"EC: {ec_sensor:.2f} (target {targets['ec_ms_cm']:.2f}) | "
f"pH: {ph_sensor:.2f} (target {targets['ph']:.2f}) | "
f"Azioni: {actions}"
)
return actions
Robotika a automatizace: ROS2 ve vertikální farmě
Robotika je nejvíce transformačním faktorem ve vertikálním zemědělství v roce 2025. Ruční operace intenzivnější je výsev (výsev do podnosů), přesazování (přesazování sazenic do konečné regály), sklizeň a balení. Jeden dělník dokáže přesadit kolem 500-700 rostlin prozatím; transplantační robot pracuje rychlostí 2 000–3 000 rostlin za hodinu s nižší chybovostí na 1 %. S 30 000 tácy salátu denně (jako v případě Agricola Moderna v Agnadello), robotika není volba, je to nutnost.
ROS2 (Robot Operating System 2) se stal de facto standardem pro programování robotů ve vnitřním prostředí. Ve srovnání s ROS1 nabízí nativní podporu v reálném čase (DDS middleware), Vylepšená architektura zabezpečení, nativní podpora více robotů a spravované uzly životního cyklu. Struktura uzlu a tématu umožňuje jasné oddělení logiky plánování pohyb, ovládání motoru, umělé vidění a rozhraní se systémem hospodaření na farmě.
"""
ROS2 Node per Harvesting Robot in Vertical Farm
Gestisce pianificazione percorso, prelievo e deposito vassoi
"""
import rclpy
from rclpy.node import Node
from rclpy.action import ActionServer
from geometry_msgs.msg import Pose, PoseStamped
from std_msgs.msg import String, Bool
from sensor_msgs.msg import Image
import json
import asyncio
# Messaggi custom per la farm (definiti in farm_interfaces package)
# from farm_interfaces.msg import TrayInfo, HarvestStatus
# from farm_interfaces.action import HarvestTray
# from farm_interfaces.srv import GetZoneLayout
class HarvestingRobotNode(Node):
"""
Nodo ROS2 per robot di raccolta in vertical farm.
Si interfaccia con:
- Sistema SCADA per ricevere job di raccolta
- Controller braccio robotico (MoveIt2)
- Sistema conveyor per deposito vassoi
- Computer vision per verifica maturita
"""
def __init__(self):
super().__init__('harvesting_robot_node')
# Publisher stato robot
self.status_pub = self.create_publisher(
String, '/farm/robot/harvest/status', 10
)
# Subscriber per job di raccolta da SCADA
self.job_sub = self.create_subscription(
String, '/farm/scada/harvest_jobs',
self.on_harvest_job, 10
)
# Subscriber per immagine camera end-effector
self.camera_sub = self.create_subscription(
Image, '/robot/camera/raw',
self.on_camera_frame, 10
)
# Client per servizio layout zona
# self.layout_client = self.create_client(GetZoneLayout, '/farm/zone/layout')
self.current_job: dict = {}
self.is_busy = False
self.get_logger().info('HarvestingRobotNode avviato')
def on_harvest_job(self, msg: String):
"""Riceve job di raccolta dallo SCADA"""
if self.is_busy:
self.get_logger().warn('Robot occupato, job ignorato')
return
try:
job = json.loads(msg.data)
self.get_logger().info(
f"Job ricevuto: zona={job['zone_id']}, "
f"tray={job['tray_id']}, "
f"crop={job['crop_type']}"
)
self.current_job = job
self.is_busy = True
# Avvia sequenza raccolta in thread separato
self.executor.create_task(self.execute_harvest(job))
except (json.JSONDecodeError, KeyError) as e:
self.get_logger().error(f"Job malformato: {e}")
async def execute_harvest(self, job: dict) -> bool:
"""
Sequenza completa raccolta:
1. Naviga verso zona target
2. Verifica maturita con visione artificiale
3. Preleva vassoio con braccio robotico
4. Trasporta a conveyor di uscita
5. Aggiorna SCADA
"""
try:
# Step 1: Navigazione
self.publish_status("NAVIGATING", job)
success = await self.navigate_to_zone(job['zone_id'], job['shelf_row'])
if not success:
self.publish_status("NAV_FAILED", job)
return False
# Step 2: Verifica maturita (computer vision)
maturity_score = await self.check_crop_maturity(job['tray_id'])
if maturity_score < 0.85:
self.get_logger().warn(
f"Vassoio {job['tray_id']}: maturita {maturity_score:.2f} "
f"sotto soglia 0.85, raccolta posticipata"
)
self.publish_status("MATURITY_INSUFFICIENT", job)
self.is_busy = False
return False
# Step 3: Raccolta
self.publish_status("HARVESTING", job)
await self.pick_tray(job['tray_id'], job['shelf_position'])
# Step 4: Deposito su conveyor
self.publish_status("DELIVERING", job)
await self.deliver_to_conveyor(job['destination_line'])
# Step 5: Completamento
self.publish_status("COMPLETED", job)
self.is_busy = False
return True
except Exception as e:
self.get_logger().error(f"Errore harvest job {job.get('tray_id')}: {e}")
self.publish_status("ERROR", job)
self.is_busy = False
return False
def publish_status(self, status: str, job: dict):
msg = String()
msg.data = json.dumps({
"robot_id": self.get_name(),
"status": status,
"tray_id": job.get("tray_id"),
"zone_id": job.get("zone_id"),
"timestamp": self.get_clock().now().to_msg().sec
})
self.status_pub.publish(msg)
async def navigate_to_zone(self, zone_id: str, shelf_row: int) -> bool:
"""Naviga AGV verso la zona target (stub - usa Nav2 in produzione)"""
self.get_logger().info(f"Navigazione verso zona {zone_id} fila {shelf_row}")
await asyncio.sleep(2.0) # simulazione movimento
return True
async def check_crop_maturity(self, tray_id: str) -> float:
"""Analisi visione artificiale per valutazione maturita (stub)"""
# In produzione: inferenza YOLO/custom model su immagine camera
await asyncio.sleep(0.5)
return 0.92 # score maturita 0-1
async def pick_tray(self, tray_id: str, position: dict) -> bool:
"""Controllo braccio robotico per prelievo vassoio via MoveIt2 (stub)"""
await asyncio.sleep(1.5)
return True
async def deliver_to_conveyor(self, destination_line: str) -> bool:
"""Deposita vassoio su conveyor di uscita (stub)"""
await asyncio.sleep(1.0)
return True
def on_camera_frame(self, msg: Image):
"""Callback per frame camera (elaborato async su richiesta)"""
pass
def main(args=None):
rclpy.init(args=args)
node = HarvestingRobotNode()
try:
rclpy.spin(node)
except KeyboardInterrupt:
pass
finally:
node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
Návrh API pro vertikální farmu: Backend FastAPI REST
Vrstva API je integračním bodem mezi fyzickým řídicím systémem farmy a systémem vnější svět: firemní ERP, zákaznický portál, mobilní aplikace operátora, WMS systém distribuční sklad. Špatně navržené API v tomto kontextu vede k nekonzistencím v recepturách plodin, chybách plánování a potenciálně ztrátě plodin. Dobrý API je naopak nervový systém, který koordinuje všechny subsystémy.
"""
FastAPI Backend per Gestione Vertical Farm
Endpoints: ricette colturali, zone, batch produzione, attuatori, sensori
"""
from fastapi import FastAPI, HTTPException, Depends, BackgroundTasks, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from pydantic import BaseModel, Field, validator
from typing import Optional, List
from datetime import datetime, date
from enum import Enum
import uuid
app = FastAPI(
title="Vertical Farm Control API",
description="API per gestione vertical farm: ricette, zone, robot, sensori",
version="2.1.0"
)
security = HTTPBearer()
# ============================================================
# MODELLI PYDANTIC
# ============================================================
class CropTypeEnum(str, Enum):
LETTUCE = "lettuce"
BASIL = "basil"
SPINACH = "spinach"
STRAWBERRY = "strawberry"
MICROGREENS = "microgreens"
HERBS = "herbs"
class GrowingSystemEnum(str, Enum):
NFT = "nft"
DWC = "dwc"
AEROPONICS = "aeroponics"
SUBSTRATE = "substrate"
class LightRecipeCreate(BaseModel):
name: str = Field(..., min_length=3, max_length=100)
crop_type: CropTypeEnum
growth_stage: str
ppfd_target: float = Field(..., ge=50, le=800)
dli_target: float = Field(..., ge=5, le=40)
photoperiod_hours: float = Field(..., ge=8, le=24)
spectrum_red_pct: float = Field(..., ge=0, le=100)
spectrum_blue_pct: float = Field(..., ge=0, le=100)
spectrum_white_pct: float = Field(..., ge=0, le=100)
spectrum_farred_pct: float = Field(default=0.0, ge=0, le=20)
spectrum_uv_pct: float = Field(default=0.0, ge=0, le=10)
notes: Optional[str] = None
@validator('spectrum_blue_pct')
def validate_spectrum_sum(cls, v, values):
total = (values.get('spectrum_red_pct', 0) + v +
values.get('spectrum_white_pct', 0))
# Tolleranza +/- 5% per arrotondamenti
if total > 105:
raise ValueError(f"Somma canali spettro {total}% supera 100%")
return v
class BatchCreate(BaseModel):
zone_id: str
recipe_id: str
crop_type: CropTypeEnum
growing_system: GrowingSystemEnum
seeding_date: date
expected_harvest_date: date
tray_count: int = Field(..., ge=1, le=10000)
seeds_per_tray: int = Field(default=50, ge=1, le=500)
client_order_id: Optional[str] = None
@validator('expected_harvest_date')
def harvest_after_seeding(cls, v, values):
seeding = values.get('seeding_date')
if seeding and v <= seeding:
raise ValueError("La data di raccolta deve essere successiva alla semina")
return v
class SensorReading(BaseModel):
zone_id: str
timestamp: datetime
temperature_c: float
humidity_rh: float
co2_ppm: float
ppfd_umol: Optional[float] = None
ec_ms_cm: Optional[float] = None
ph: Optional[float] = None
water_temp_c: Optional[float] = None
class ActuatorCommand(BaseModel):
zone_id: str
command_type: str # "led_update", "co2_valve", "pump_speed", "hvac"
parameters: dict
priority: int = Field(default=5, ge=1, le=10) # 10 = emergenza
# ============================================================
# ROUTES: RICETTE COLTURALI
# ============================================================
@app.post("/api/v1/recipes", status_code=status.HTTP_201_CREATED)
async def create_recipe(
recipe: LightRecipeCreate,
credentials: HTTPAuthorizationCredentials = Depends(security)
):
"""
Crea una nuova ricetta colturale nel sistema.
Le ricette definiscono parametri luminosi, ambientali e irrigazione.
"""
recipe_id = str(uuid.uuid4())
# In produzione: salvataggio su PostgreSQL
return {
"recipe_id": recipe_id,
"name": recipe.name,
"crop_type": recipe.crop_type,
"ppfd_target": recipe.ppfd_target,
"dli_target": recipe.dli_target,
"created_at": datetime.utcnow().isoformat(),
"status": "active"
}
@app.get("/api/v1/recipes/{recipe_id}")
async def get_recipe(recipe_id: str):
"""Recupera ricetta colturale per ID"""
# Stub - in produzione: query PostgreSQL
return {
"recipe_id": recipe_id,
"name": "Lattuga Lollo Rossa - Vegetativo",
"crop_type": "lettuce",
"ppfd_target": 200.0,
"dli_target": 17.0,
"photoperiod_hours": 16.0,
"spectrum": {"red": 65, "blue": 20, "white": 10, "farred": 5},
"env_targets": {"temp_c": 21.0, "humidity_rh": 70.0, "co2_ppm": 1200},
"nutrient_targets": {"ec_ms_cm": 1.6, "ph": 6.0}
}
@app.get("/api/v1/recipes")
async def list_recipes(
crop_type: Optional[CropTypeEnum] = None,
active_only: bool = True,
limit: int = Field(default=50, le=200)
):
"""Lista ricette con filtro per tipo coltura"""
return {
"recipes": [],
"total": 0,
"filters": {"crop_type": crop_type, "active_only": active_only}
}
# ============================================================
# ROUTES: BATCH PRODUZIONE
# ============================================================
@app.post("/api/v1/batches", status_code=status.HTTP_201_CREATED)
async def create_batch(
batch: BatchCreate,
background_tasks: BackgroundTasks,
credentials: HTTPAuthorizationCredentials = Depends(security)
):
"""
Avvia un nuovo batch di produzione.
Associa zona, ricetta, dati di seeding e target harvest.
Background: programma scheduling LED e irrigazione su SCADA.
"""
batch_id = str(uuid.uuid4())
background_tasks.add_task(schedule_batch_on_scada, batch_id, batch)
return {
"batch_id": batch_id,
"zone_id": batch.zone_id,
"crop_type": batch.crop_type,
"seeding_date": batch.seeding_date.isoformat(),
"expected_harvest_date": batch.expected_harvest_date.isoformat(),
"tray_count": batch.tray_count,
"status": "scheduled"
}
async def schedule_batch_on_scada(batch_id: str, batch: BatchCreate):
"""Task background: invia configurazione a SCADA per scheduling"""
# In produzione: chiamata API verso SCADA (Ignition, custom Python SCADA)
pass
# ============================================================
# ROUTES: SENSORI E TELEMETRIA
# ============================================================
@app.post("/api/v1/telemetry")
async def ingest_sensor_data(reading: SensorReading):
"""
Endpoint per ingestion dati sensori da edge controller.
Validazione, allarmi e storicizzazione su InfluxDB/TimescaleDB.
"""
alerts = []
# Allarmi temperatura
if reading.temperature_c > 28.0:
alerts.append({"type": "HIGH_TEMP", "value": reading.temperature_c, "threshold": 28.0})
elif reading.temperature_c < 16.0:
alerts.append({"type": "LOW_TEMP", "value": reading.temperature_c, "threshold": 16.0})
# Allarmi CO2
if reading.co2_ppm > 2000:
alerts.append({"type": "HIGH_CO2", "value": reading.co2_ppm, "threshold": 2000})
# Allarmi pH
if reading.ph is not None and (reading.ph < 5.0 or reading.ph > 7.5):
alerts.append({"type": "PH_OUT_OF_RANGE", "value": reading.ph})
# In produzione: write batch su InfluxDB e publish alert su Kafka/MQTT
return {
"status": "accepted",
"zone_id": reading.zone_id,
"timestamp": reading.timestamp.isoformat(),
"alerts": alerts,
"alert_count": len(alerts)
}
@app.get("/api/v1/zones/{zone_id}/current")
async def get_zone_current_state(zone_id: str):
"""Stato ambientale corrente di una zona (last value da InfluxDB)"""
# Stub
return {
"zone_id": zone_id,
"timestamp": datetime.utcnow().isoformat(),
"sensors": {
"temperature_c": 21.3,
"humidity_rh": 69.8,
"co2_ppm": 1185,
"ppfd_umol": 198.5,
"ec_ms_cm": 1.62,
"ph": 6.05
},
"actuators": {
"led_pwm": {"red": 0.617, "blue": 0.192, "white": 0.098, "farred": 0.049},
"co2_valve_pct": 12.5,
"hvac_cooling_pct": 35.0,
"pump_active": True
},
"active_batch_id": "b-2025-001-lollo",
"days_since_seeding": 18
}
# ============================================================
# ROUTES: CONTROLLO ATTUATORI
# ============================================================
@app.post("/api/v1/actuators/command")
async def send_actuator_command(
command: ActuatorCommand,
credentials: HTTPAuthorizationCredentials = Depends(security)
):
"""
Invia comando manuale a un attuatore di zona.
Usato per override manuali, manutenzione e test.
Richiede autenticazione e viene loggato per audit.
"""
allowed_commands = {"led_update", "co2_valve", "pump_speed", "hvac", "emergency_stop"}
if command.command_type not in allowed_commands:
raise HTTPException(
status_code=400,
detail=f"Tipo comando non valido: {command.command_type}"
)
command_id = str(uuid.uuid4())
# In produzione: publish su MQTT/OPC-UA verso edge controller
return {
"command_id": command_id,
"zone_id": command.zone_id,
"command_type": command.command_type,
"parameters": command.parameters,
"status": "sent",
"sent_at": datetime.utcnow().isoformat()
}
Farm Digital Twin: Simulace a optimalizace
Digitální dvojče vertikální farmy a výpočtový model, který toto chování replikuje farmářskou fyziku dostatečně přesně, aby umožňovala prediktivní simulace. Ne ano Není to vizuální 3D replika (to je "vizualizace"), ale matematický model který při současném stavu parametrů prostředí předpovídá růst rostlin a čas sklizně.
Nejpoužívanější modely růstu rostlin v kontrolovaném prostředí jsou založeny na tomto přístupu účinnost využití záření (RUE): akumulovaná biomasa je úměrná zachycené světlo (PAR) a účinnost konverze, která závisí na teplotě, CO2, dostupnost vody a výživy. Tyto modely byly původně vyvinuty pro systémy předpovědi výnosů na otevřených polích (např. DSSAT, APSIM), byly přizpůsobeny životnímu prostředí vnitřní s experimentálně kalibrovanými parametry.
"""
Digital Twin - Modello di Crescita Vegetale per Vertical Farm
Basato su Radiation Use Efficiency (RUE) + effetti temperatura/CO2
"""
import numpy as np
from dataclasses import dataclass, field
from typing import List, Optional
from datetime import datetime, timedelta
@dataclass
class PlantGrowthModel:
"""
Modello semplificato di crescita per lattuga in sistema idroponico.
Parametri calibrati su dati sperimentali per Lactuca sativa.
"""
# Parametri biologici della coltura
rue_base: float = 1.8 # g biomassa / MJ PAR intercettato
temp_base: float = 5.0 # temperatura base (°C) - sotto non cresce
temp_opt: float = 22.0 # temperatura ottimale
temp_max: float = 32.0 # temperatura max sopravvivenza
co2_base_ppm: float = 400.0 # CO2 ambient reference
co2_enhancement: float = 0.002 # incremento RUE per ppm CO2 extra
# Stato corrente della pianta
fresh_weight_g: float = 0.5 # peso fresco iniziale (semenzale 5g DW)
dry_weight_g: float = 0.05 # peso secco iniziale
leaf_area_cm2: float = 5.0 # area fogliare iniziale
days_since_seeding: int = 0
# Target harvest
target_fresh_weight_g: float = 150.0 # lattuga da 150g
water_content: float = 0.95 # % acqua rispetto al peso fresco
def temperature_factor(self, temp: float) -> float:
"""
Fattore temperatura (0-1) usando funzione beta.
temp_opt da il massimo rendimento (1.0).
"""
if temp <= self.temp_base or temp >= self.temp_max:
return 0.0
if temp <= self.temp_opt:
return (temp - self.temp_base) / (self.temp_opt - self.temp_base)
else:
return (self.temp_max - temp) / (self.temp_max - self.temp_opt)
def co2_factor(self, co2_ppm: float) -> float:
"""Fattore arricchimento CO2 (1.0 ad ambient, >1 con arricchimento)"""
extra_co2 = max(0, co2_ppm - self.co2_base_ppm)
return 1.0 + (self.co2_enhancement * extra_co2)
def par_intercepted_mj(self, ppfd: float, leaf_area_cm2: float,
photoperiod_h: float) -> float:
"""
Calcola PAR intercettata dalla pianta in MJ/giorno.
ppfd: µmol/m²/s -> conversione a W/m² (1 W/m² ≈ 4.6 µmol/m²/s per LED)
"""
ppfd_wm2 = ppfd / 4.6
par_w = ppfd_wm2 * (leaf_area_cm2 / 10000) # in m²
par_mj_day = par_w * photoperiod_h * 3600 / 1e6
return par_mj_day
def simulate_day(self, temp: float, co2_ppm: float,
ppfd: float, photoperiod_h: float) -> dict:
"""
Simula un giorno di crescita e aggiorna lo stato della pianta.
Restituisce delta giornaliero e stato aggiornato.
"""
# Fattori ambientali
tf = self.temperature_factor(temp)
cf = self.co2_factor(co2_ppm)
par_intercepted = self.par_intercepted_mj(ppfd, self.leaf_area_cm2, photoperiod_h)
# Crescita biomassa secca (RUE model)
delta_dw = self.rue_base * par_intercepted * tf * cf
delta_fw = delta_dw / (1 - self.water_content)
self.dry_weight_g += delta_dw
self.fresh_weight_g += delta_fw
# Aggiornamento area fogliare (SLA - specific leaf area)
sla_cm2_per_g = 350 # cm²/g DW per lattuga
self.leaf_area_cm2 = self.dry_weight_g * sla_cm2_per_g
self.days_since_seeding += 1
# Check harvest readiness
harvest_ready = self.fresh_weight_g >= self.target_fresh_weight_g
return {
"day": self.days_since_seeding,
"fresh_weight_g": round(self.fresh_weight_g, 2),
"dry_weight_g": round(self.dry_weight_g, 3),
"leaf_area_cm2": round(self.leaf_area_cm2, 1),
"delta_fw_g": round(delta_fw, 3),
"temp_factor": round(tf, 3),
"co2_factor": round(cf, 3),
"par_intercepted_mj": round(par_intercepted, 6),
"harvest_ready": harvest_ready,
}
def simulate_full_cycle(self, daily_conditions: List[dict]) -> dict:
"""
Simula l'intero ciclo colturale con condizioni giornaliere variabili.
Restituisce proiezione completa e giorno stimato di raccolta.
"""
days_log = []
harvest_day = None
for day_idx, cond in enumerate(daily_conditions):
day_state = self.simulate_day(
temp=cond.get('temp', 21.0),
co2_ppm=cond.get('co2_ppm', 1200),
ppfd=cond.get('ppfd', 200),
photoperiod_h=cond.get('photoperiod_h', 16)
)
days_log.append(day_state)
if day_state['harvest_ready'] and harvest_day is None:
harvest_day = day_idx + 1
return {
"days_simulated": len(days_log),
"final_fresh_weight_g": self.fresh_weight_g,
"estimated_harvest_day": harvest_day,
"daily_log": days_log,
"achieved_target": self.fresh_weight_g >= self.target_fresh_weight_g
}
# Esempio utilizzo digital twin
def predict_harvest_date(recipe: dict, seeding_date: datetime) -> datetime:
"""
Usa il digital twin per predire la data di raccolta
dato una ricetta ambientale costante.
"""
model = PlantGrowthModel()
# Condizioni giornaliere dalla ricetta (costanti per semplicità)
daily_conditions = [{
'temp': recipe.get('temp_c', 21.0),
'co2_ppm': recipe.get('co2_ppm', 1200),
'ppfd': recipe.get('ppfd_target', 200),
'photoperiod_h': recipe.get('photoperiod_hours', 16)
}] * 40 # massimo 40 giorni di simulazione
result = model.simulate_full_cycle(daily_conditions)
harvest_day = result.get('estimated_harvest_day', 35)
return seeding_date + timedelta(days=harvest_day)
Umělá inteligence pro optimalizaci: Posílení učení pro jasné recepty
Digitální dvojče umožňuje něco mocnějšího než jednoduchá předpověď: optimalizace receptury prostřednictvím posilovacího učení (RL). Agent RL interaguje s digitálním dvojčetem (nikoli se skutečnou farmou), zkoumá tisíce kombinací světelné parametry a najděte konfigurace, které maximalizují výnos a zároveň minimalizují spotřebu energický. Jakmile je v simulaci nalezen optimální recept, je v jednom ověřen pilotní oblast skutečné farmy před nasazením měřítka.
Tento přístup, řekl převod ze sim do reálnéhoa hranice výzkumu ve vertikálním zemědělství AI. Propast mezi simulací a realitou (mezera mezi simulací a realitou) vyžaduje a průběžná kalibrace růstového modelu na reálných datech shromážděných z farmy.
"""
Reinforcement Learning per Ottimizzazione Ricette Luminose
Usa Gymnasium + custom environment basato sul PlantGrowthModel
"""
import gymnasium as gym
import numpy as np
from gymnasium import spaces
from typing import Tuple, Optional
class VerticalFarmEnv(gym.Env):
"""
Ambiente Gymnasium per ottimizzazione ricette luminose.
Observation space: stato ambientale corrente + stato pianta
Action space: aggiustamenti parametri LED (continuo)
Reward: crescita giornaliera / consumo energetico
"""
metadata = {'render_modes': ['human']}
def __init__(self, crop_type: str = "lettuce", episode_days: int = 30):
super().__init__()
self.crop_type = crop_type
self.episode_days = episode_days
self.current_day = 0
# Action space: [delta_ppfd, delta_red_ratio, delta_blue_ratio, delta_photoperiod]
# Valori normalizzati in [-1, 1], scalati internamente
self.action_space = spaces.Box(
low=np.array([-1.0, -1.0, -1.0, -1.0], dtype=np.float32),
high=np.array([1.0, 1.0, 1.0, 1.0], dtype=np.float32)
)
# Observation space: [ppfd, red_ratio, blue_ratio, photoperiod,
# fresh_weight, leaf_area, days, temp, co2]
self.observation_space = spaces.Box(
low=np.array([50, 0, 0, 8, 0, 0, 0, 15, 400], dtype=np.float32),
high=np.array([800, 1, 1, 24, 500, 5000, 45, 30, 2000], dtype=np.float32)
)
# Stato corrente
self.ppfd = 200.0
self.red_ratio = 0.65
self.blue_ratio = 0.20
self.photoperiod = 16.0
self.plant_model = None
def reset(self, seed: Optional[int] = None, **kwargs) -> Tuple[np.ndarray, dict]:
super().reset(seed=seed)
self.current_day = 0
self.ppfd = 200.0
self.red_ratio = 0.65
self.blue_ratio = 0.20
self.photoperiod = 16.0
from digital_twin import PlantGrowthModel # import locale
self.plant_model = PlantGrowthModel()
return self._get_obs(), {}
def step(self, action: np.ndarray) -> Tuple[np.ndarray, float, bool, bool, dict]:
# Applica azione con scaling
self.ppfd = np.clip(self.ppfd + action[0] * 50, 50, 800)
self.red_ratio = np.clip(self.red_ratio + action[1] * 0.1, 0.3, 0.8)
self.blue_ratio = np.clip(self.blue_ratio + action[2] * 0.05, 0.1, 0.35)
self.photoperiod = np.clip(self.photoperiod + action[3] * 1.0, 10, 22)
# Simula giorno con nuove condizioni
day_result = self.plant_model.simulate_day(
temp=21.0, co2_ppm=1200,
ppfd=self.ppfd, photoperiod_h=self.photoperiod
)
# Calcola consumo energetico (kWh/giorno per m² crescita)
energy_kwh = (self.ppfd / 4.6) * (self.photoperiod / 1000) # semplificato
# Reward: crescita / energia (massimizza efficienza)
growth = day_result['delta_fw_g']
reward = growth / max(energy_kwh, 0.001) * 0.01
# Penalita per harvest_ready raggiunto troppo tardi
if self.current_day > 35 and not day_result['harvest_ready']:
reward -= 5.0
# Bonus per harvest_ready raggiunto nei tempi
if day_result['harvest_ready'] and self.current_day <= 28:
reward += 20.0
self.current_day += 1
done = day_result['harvest_ready'] or self.current_day >= self.episode_days
return self._get_obs(), reward, done, False, day_result
def _get_obs(self) -> np.ndarray:
pm = self.plant_model
return np.array([
self.ppfd, self.red_ratio, self.blue_ratio, self.photoperiod,
pm.fresh_weight_g if pm else 0.5,
pm.leaf_area_cm2 if pm else 5.0,
self.current_day, 21.0, 1200.0
], dtype=np.float32)
# Training con Stable-Baselines3
# from stable_baselines3 import PPO
# env = VerticalFarmEnv(crop_type="lettuce")
# model = PPO("MlpPolicy", env, verbose=1, learning_rate=3e-4)
# model.learn(total_timesteps=500_000)
# model.save("optimized_lettuce_recipe_v1")
Infrastruktura průmyslového internetu věcí: Modbus, MQTT, OPC-UA
Vertikální farmy používají překrytí průmyslových protokolů: Modbus RTU/TCP for komunikace se staršími senzory a akčními členy (termohygrometry, měřiče CO2, ovladače LED), OPC-UA pro komunikaci s PLC Siemens/Beckhoff a SCADA systémy, MQTT pro odesílání dat do cloudu. Výběr závisí na hardwaru dodavatele, latenci požadované a úroveň zabezpečení.
Protokoly IoT ve vertikálním zemědělství: Srovnání
| Protokol | Vrstvy | Latence | Bezpečnost | Typický případ použití |
|---|---|---|---|---|
| Modbus RTU | Field-PLC | 10-100 ms | Chybí (starší) | EC/pH senzory, LED drivery |
| Modbus TCP | PLC-SCADA | 5-50 ms | Volitelné TLS | Sběr dat PLC |
| OPC-UA | PLC-SCADA-Cloud | 1-50 ms | X.509, podpis, šifrování | Standardy Industry 4.0 |
| MQTT | Edge Cloud | 10-500 ms | TLS + autentizace | Telemetrie do cloudu |
| REST/HTTP | Cloud-Cloud | 50-500 ms | HTTPS, OAuth2 | API pro integraci ERP |
"""
Bridge Modbus -> MQTT per vertical farm
Legge sensori via Modbus RTU e pubblica su MQTT broker
"""
import asyncio
import json
import time
import logging
from pymodbus.client import AsyncModbusSerialClient
import paho.mqtt.client as mqtt
logger = logging.getLogger(__name__)
# Mappa registri Modbus per sensore combo Temp/RH/CO2 (esempio Vaisala HMP60)
MODBUS_REGISTER_MAP = {
"temperature": {"address": 0x0000, "count": 1, "scale": 0.1, "unit": "°C"},
"humidity": {"address": 0x0001, "count": 1, "scale": 0.1, "unit": "%RH"},
"co2_ppm": {"address": 0x0002, "count": 1, "scale": 1.0, "unit": "ppm"},
"ec_ms_cm": {"address": 0x0010, "count": 1, "scale": 0.01, "unit": "mS/cm"},
"ph": {"address": 0x0011, "count": 1, "scale": 0.01, "unit": "pH"},
}
class ModbusMQTTBridge:
def __init__(self, zone_id: str, modbus_port: str,
modbus_address: int, mqtt_broker: str, mqtt_port: int = 1883):
self.zone_id = zone_id
self.modbus_client = AsyncModbusSerialClient(
port=modbus_port, baudrate=9600, timeout=3
)
self.mqtt_client = mqtt.Client(client_id=f"bridge-{zone_id}")
self.mqtt_broker = mqtt_broker
self.mqtt_port = mqtt_port
self.mqtt_topic = f"farm/zones/{zone_id}/telemetry"
async def connect(self):
await self.modbus_client.connect()
self.mqtt_client.connect(self.mqtt_broker, self.mqtt_port, keepalive=60)
self.mqtt_client.loop_start()
logger.info(f"Bridge avviato per zona {self.zone_id}")
async def read_all_sensors(self, device_id: int = 1) -> dict:
readings = {"zone_id": self.zone_id, "timestamp": time.time()}
for sensor_name, reg in MODBUS_REGISTER_MAP.items():
try:
result = await self.modbus_client.read_holding_registers(
address=reg["address"],
count=reg["count"],
slave=device_id
)
if not result.isError():
raw_value = result.registers[0]
readings[sensor_name] = round(raw_value * reg["scale"], 3)
else:
logger.warning(f"Errore lettura {sensor_name} zona {self.zone_id}")
readings[sensor_name] = None
except Exception as e:
logger.error(f"Eccezione Modbus {sensor_name}: {e}")
readings[sensor_name] = None
return readings
async def publish_loop(self, interval_sec: float = 30.0):
while True:
readings = await self.read_all_sensors()
payload = json.dumps(readings)
result = self.mqtt_client.publish(
topic=self.mqtt_topic,
payload=payload,
qos=1
)
if result.rc == mqtt.MQTT_ERR_SUCCESS:
logger.debug(f"Pubblicato su {self.mqtt_topic}: {payload[:80]}...")
else:
logger.error(f"Errore publish MQTT: rc={result.rc}")
await asyncio.sleep(interval_sec)
Ekonomika vertikálního zemědělství: CAPEX, OPEX a Break-Even
Je vertikální zemědělství ekonomicky udržitelné? Odpověď v roce 2025 zní: Závisí. Záleží na úrodě (bylinky a microgreens jsou mnohem výnosnější než hlávkový salát), na měřítko (úspory z rozsahu se objevují nad 5 000 m²), umístění (náklady rozhodující jsou místní náklady na energii a práci) a prodejním kanálem (přímý prodej). B2C za prémiové ceny vs GDO komodity).
Ekonomická analýza: Farma 1 000 m² čistého obdělávání
| Hlas | Hodnota | Poznámky |
|---|---|---|
| CAPEX (počáteční investice) | ||
| Struktura a systém | 800 000 € | Rekonstrukce skladu |
| NFT police a kanály | 300 000 € | Hydroponické systémy |
| LED osvětlení | 600 000 € | 600W/m² účinnost 2,8 µmol/J |
| HVAC a klima | 250 000 € | Chlazení + odvlhčování |
| Systém CO2 | 50 000 € | Skladování + distribuce |
| Automatizace a robotizace | 400 000 € | Secí stroj, přesazovač, kombajn |
| Software a integrace | 150 000 € | SCADA, API, digitální dvojče |
| Celkové CAPEX | 2 550 000 € | ~2 550 EUR/m² |
| ROČNÍ OPEX | ||
| Elektřina | 420 000 € | 35% OPEX - hlavní kritická energie |
| Práce (10 operátorů) | 350 000 € | 27% OPEX |
| Semena a substrát | 80 000 € | 6% OPEX |
| Živiny a CO2 | 60 000 € | 5% OPEX |
| Údržba | 90 000 € | 7% OPEX |
| Balení a logistika | 120 000 € | 9% OPEX |
| Ostatní (pojištění atd.) | 80 000 € | 6% OPEX |
| Celkem OPEX | 1 200 000 € | 1 200 €/m²/rok |
| PŘÍJMY | ||
| Výroba salátu (18 cyklů x 25 kg/m²) | 450 kg/m²/rok | Celkem 450 000 kg |
| Prémiová prodejní cena GDO | 3,5 €/kg | oproti 0,8–1,2 EUR venku |
| Hrubé příjmy | 1 575 000 € | |
| EBITDA | 375 000 € | marže 23,8 %. |
| CAPEX amortizace (10 let) | 255 000 € | |
| Čistý zisk před zdaněním | 120 000 € | marže 7,6 %. |
| Break-even (roky) | ~8-10 let | S bylinkami: 4-5 let |
Energetický problém ve vertikálním zemědělství
Energetická a existenční výzva vertikálního zemědělství. Nejúčinnější LED dostupné v roce 2025 dosahují cca 3,0-3,5 umol/J fotonické účinnosti. K výrobě a DLI 17 mol/m²/den s 16 hodinami fotoperiody vyžaduje přibližně 280 Wh/m²/den, tzn. 102 kWh/m²/rok jen na osvětlení. S rozlohou 1 000 m² a nákladem energie 0,15 €/kWh (průmyslový tarif Itálie 2025), účet za LED je již 153 000 €/rok. Přidejte HVAC (obvykle 60-70 % energie LED), CO2, čerpadla, automatizaci – máte to snadno za 420 000 €/rok. Ti, kteří mají přístup k levné obnovitelné energii (např Moderní se 100% obnovitelnými zdroji energie) nebo má nainstalovanou významnou část fotovoltaických krytů potřeby, ale ne všechny.
Vertikální zemědělství není udržitelná za všechny ceny energie. S energií nad 0,25 EUR/kWh (možný scénář v krizových obdobích, jako jsou roky 2022–2023), mnoho ekonomických modelů zhroutí se. Sázka je na to, že účinnost LED se stále zlepšuje a obnovitelná energie stojí méně a méně.
Případová studie: Planetární farmy a moderní zemědělství – italský model
Itálie je domovem dvou nejpokročilejších evropských projektů vertikálního zemědělství, oba s sídlící v milánském vnitrozemí. Jejich cesty jsou různé, ale doplňují se a představují dva přístupy k problému škálovatelnosti a ziskovosti v evropském vertikálním zemědělství.
Planetární farmy - Cavenago di Brianza
Společnost Planet Farms, kterou v roce 2018 založili Luca Travaglini a Massimiliano Loschi, vybudovala první továrna v bývalé průmyslové oblasti Cavenago (MB). Původní rostlina z 9 000 m² a v roce 2024 se stal jedním z největších v Evropě 20 000 m² růstový povrch. V listopadu 2023 zvýšila společnost Planet Farms kolo ve výši 40 milionů USD při ocenění 500 milionů USD, jeden z největších v Evropě v tomto sektoru. Partnerství se Swiss Life Asset Managers v roce 2025 vytvořilo JV z 200 € miliony rozvíjet vertikální farmy ve velkém v celé Evropě.
Technologické partnerství se společností Siemens je srdcem automatizace: řídicí systémy průmyslové Siemens S7-1500 řídí smyčky životního prostředí, zatímco platforma Mindsphere (nyní Siemens Industrial Copilot) shromažďuje a analyzuje data. Produkt vlajkovými produkty jsou "Živé bylinky" - aromatické bylinky prodávané v podnosech ještě s kořen, prémiový segment s vyššími maržemi než salát.
Moderní zemědělství – Agnadello (ČR)
Agricola Moderna, založená v Miláně v roce 2018 Pierluigi Giuliani a Benjamin Franchetti, nová továrna byla otevřena v září 2024 11 000 m² reklama Agnadello (Cremona), financované půjčkou 10 milionů EUR od společnosti Intesa Sanpaolo. Rostlina produkuje 30 000 pytlů salátu denně, napájen 100% z obnovitelných zdrojů, s interní AI vyvinutou týmem R&D pro optimalizovat receptury.
Technologická diferenciace Agricola Moderna a hyperspektrálního zobrazovacího systému (partnerství se Specim), které umožňuje vyhodnotit nutriční stav rostlin než jsou příznaky viditelné lidským okem. RGB-D kamery pro rekonstrukci 3D vrchlíku, senzory prostředí s tisíci měřicích bodů a algoritmy Interně vyvinuté systémy počítačového vidění tvoří sadu proprietárních technologií.
Srovnání Planet Farms vs Moderní Agricola
| Parametr | Planetární farmy | Moderní zemědělství |
|---|---|---|
| Povrch | 20 000 m² | 11 000 m² |
| Výroba/den | N.D. (bylinkové zaměření) | 30 000 salátových sáčků |
| Financování | 40 milionů $ kolo + 200 milionů EUR JV | 10 milionů EUR Intesa Sanpaolo |
| PLC/automatizace | Siemens S7-1500 | Beckhoff + vlastní |
| Platforma AI | Siemens Industrial Copilot | Vlastní vývoj |
| Počítačové vidění | RGB standardy | Hyperspektrální (Specim) |
| Energie | Částečně obnovitelné | 100% obnovitelné |
| Cílový trh | Prémiový maloobchodní GDO + expanze do Spojeného království | Italský velkoobchod ve velkém měřítku (řada IV) |
Výzvy, omezení a realita sektoru
Upřímný článek o vertikálním zemědělství musí obsahovat skutečné výzvy, nejen výhody. Sektor zažil v letech 2022–2024 velkou fázi deziluze s bankroty senzační, které spálily miliardy dolarů investic. Pochopení proč je zásadní budovat udržitelné systémy.
Skutečné výzvy vertikálního zemědělství
- Náklady na energii: A zabiják číslo jedna. 60 % provozovatelů vertikálních farem Zatím není rentabilní, z velké části kvůli nákladům na elektřinu. Bez energie nízkonákladové obnovitelné zdroje, ekonomický model platí pouze pro prémiové plodiny.
- Omezené odrůdy plodin: Vertikální zemědělství je vynikající pro listová zelenina (hlávkový salát, špenát), aromatické bylinky a mikrozeleniny. Na rajčata, papriky, okurky energetický výtěžek ještě není konkurenceschopný. Obiloviny, luštěniny a kořeny jsou mimo technický a ekonomický dosah.
- Vysoké CAPEX: Profesionální instalace stojí 2 000-4 000 eur na m² kultivace. Zlomová rentabilita u salátu je 8–10 let; pro mnoho investorů příliš mnoho. Pouze bylinky a mikrozeleniny dosahují zlomového výsledku za 4-5 let.
- Závislost dodavatelského řetězce LED: Vysoce kvalitní LED čipy jsou vyrábí několik dodavatelů (Epistar, Lumileds, Osram). Narušení v dodavatelském řetězci má dopad jak na počáteční CAPEX, tak na údržbu.
- Skutečná vs vnímaná udržitelnost: Úspora vody (95 %) je reálná. Uhlíková stopa však silně závisí na energetickém mixu. Poháněná vertikální farma uhlí má horší uhlíkovou stopu než venkovní zemědělství. Pouze s obnovitelnými zdroji bilance se stává jednoznačně kladnou.
- Technická škálovatelnost: Přechod z pilotní farmy o rozloze 500 m² na a obchodní prostor 20 000 m² není jednoduché násobení. Řídící systémy, management plodin, interní logistika a robotické systémy vyžadují hluboký redesign při každém skoku v měřítku.
Nasazení ve výrobě: Kontejnerizace a monitoring
Softwarové komponenty vertikální farmy sahají od PID regulátoru v reálném čase (který běží přímo na PLC nebo na Raspberry Pi, nekontejnerované) do backendu FastAPI a AI potrubí (pohodlné bydlení v kontejnerech Docker). Typická cloudová infrastruktura kombinuje on-premise okrajovou vrstvu s rovinou ovládání cloudu.
# docker-compose.yml per stack vertical farm (dev/staging)
version: '3.9'
services:
# API principale
farm-api:
build: ./farm-api
ports:
- "8000:8000"
environment:
- DATABASE_URL=postgresql://farm:farm@postgres:5432/farmdb
- MQTT_BROKER=emqx
- INFLUXDB_URL=http://influxdb:8086
- REDIS_URL=redis://redis:6379
depends_on:
- postgres
- influxdb
- emqx
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 30s
timeout: 10s
retries: 3
# MQTT Broker
emqx:
image: emqx/emqx:5.8
ports:
- "1883:1883" # MQTT
- "8883:8883" # MQTT TLS
- "18083:18083" # Dashboard
environment:
- EMQX_NODE__COOKIE=farm-secret-cookie
volumes:
- emqx_data:/opt/emqx/data
# Time-series database per dati sensori
influxdb:
image: influxdb:2.7
ports:
- "8086:8086"
environment:
- DOCKER_INFLUXDB_INIT_MODE=setup
- DOCKER_INFLUXDB_INIT_USERNAME=admin
- DOCKER_INFLUXDB_INIT_PASSWORD=farmpass123
- DOCKER_INFLUXDB_INIT_ORG=verticalfarm
- DOCKER_INFLUXDB_INIT_BUCKET=sensors
volumes:
- influxdb_data:/var/lib/influxdb2
# PostgreSQL per dati applicativi (ricette, batch, inventory)
postgres:
image: postgres:16-alpine
environment:
- POSTGRES_DB=farmdb
- POSTGRES_USER=farm
- POSTGRES_PASSWORD=farm
volumes:
- postgres_data:/var/lib/postgresql/data
# Redis per cache e job queue
redis:
image: redis:7-alpine
command: redis-server --appendonly yes
# Grafana per dashboard real-time
grafana:
image: grafana/grafana:11.0.0
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
volumes:
- grafana_data:/var/lib/grafana
depends_on:
- influxdb
# Worker ML per digital twin e ottimizzazione
ml-worker:
build: ./ml-worker
environment:
- INFLUXDB_URL=http://influxdb:8086
- REDIS_URL=redis://redis:6379
- MODEL_PATH=/models
volumes:
- ml_models:/models
volumes:
emqx_data:
influxdb_data:
postgres_data:
grafana_data:
ml_models:
Trendy a inovace ve vertikálním zemědělství 2025-2026
Sektor se rychle rozvíjí na třech frontách: energetická účinnost, expanze plodiny a stále hlubší integrace umělé inteligence. Zde jsou nejvýznamnější trendy.
Klíčové inovace ve vertikálním zemědělství 2025
| Inovace | Dopad | Obchodní status |
|---|---|---|
| LED účinnost 4,0+ µmol/J | -20-25% nákladů na energii na osvětlení | Dostupné (LG, Signify) |
| Hyperspektrální zobrazování | Včasná diagnostika stresu rostlin, optimalizace | Předčasná adopce (Specim + Agricola M.) |
| RL pro dynamické receptury | +15-25% výnos při stejné energii | Pokročilé vyhledávání / rané prod |
| Vysokorychlostní secí robot | 3 000+ setí/hod oproti 700 manuálním | Komerční (hodně, 80 akrů) |
| Houby/pěstování hub | Diverzifikace směsi plodin, vysoká ziskovost | Obchodní růst |
| Vertikální farma jako odpadní teplo datového centra | Rekuperace tepla pro snížení HVAC | Pilot (Finsko, Německo) |
| Fotovoltaická integrace s BESS | -40-60% nákladů na energii v optimálních scénářích | Rostoucí |
Zvláště zajímavým nastupujícím trendem je integrace vertikálního zemědělství s Datová centra: Servery generují odpadní teplo, které lze rekuperovat a snížit tak náklady na vytápění skleníků v chladných měsících. Ve Finsku, kde je energetika Tuto symbiózu testuje několik pilotních projektů. Odpadní teplo při 40-60°C a ideální pro udržení kultivačních teplot v zimních sklenících bez další spotřeby energie.
Závěry: Vertikální zemědělství jako projekt softwarového inženýrství
Moderní vertikální farma a především softwarový systém. Hardware (LED diody, senzory, robot, police) je nezbytný, ale ne dostačující: transformuje se úroveň softwaru osvětlený sklad v optimalizované továrně na potraviny. PID regulátory řídí prostředí podle vědecky kalibrovaných receptur; REST API propojují farmu do komerčního ekosystému; ROS2 koordinuje roboty; digitální dvojče umožňuje simulace prediktivní; posilovací učení najde recepty, které jsou lepší než ty navržené ručně agronomy.
Vertikální zemědělský trh z 9,6 miliard v roce 2025 na 39 miliard v roce 2033 není automaticky garantováno: závisí na schopnosti odvětví vyřešit problém energie a rozšířit škálu plodin, které lze hospodárně produkovat udržitelný. Itálie s Planet Farms a Agricola Moderna je ve správné pozici být protagonistou tohoto přechodu, zejména po PNRR a pobídkách 5.0, které snižují náklady na technologické CAPEX.
Pro vývojáře, kteří chtějí vstoupit do tohoto sektoru, jsou nejžádanější dovednosti: Python pro automatizaci a ML, FastAPI pro průmyslový backend, ROS2 pro robotiku, MQTT/Modbus/OPC-UA pro průmyslové protokoly, InfluxDB pro časové řady a základy fyziologie rostlin, abyste pochopili parametry, které mají být optimalizovány. Nemusíte vědět jak zahradnictví, ale pochopit, proč rostliny rostou více s určitými vlnovými délkami je to rozdíl mezi dobrým řídicím systémem a skvělým řídicím systémem.
Nástroje a zdroje, kde se dozvíte více
- Pymodbus - Knihovna Python pro Modbus RTU/TCP, ideální pro komunikaci s PLC a senzory
- Paho-MQTT - MQTT Python klient pro integraci s brokery (EMQX, HiveMQ, Mosquitto)
- ROS2 Humble / Jazzy - Rámec robotiky, oficiální dokumentace na docs.ros.org
- FastAPI - Asynchronní backend Python, ideální pro průmyslové řídicí API
- InfluxDB 2.x - Databáze časových řad s dotazovacím jazykem Flux pro analýzu senzorů
- Stabilní základní linie3 - RL implementace (PPO, SAC, TD3) pro optimalizaci receptur
- Tělocvična - Standardní pro prostředí RL, používá se pro prostředí digitálního dvojčete
- SCADA zapalování - Cenově dostupná OEM licencovaná průmyslová platforma SCADA
- Vertikální farma denně - Hlavní zdroj zpráv o vertikálním sektoru
- Směrnice USDA CEA - Referenční parametry pro kontrolované prostředí zemědělství
Další v řadě: Prognóza poptávky pro maloobchod s potravinami
V článku 8 se zabýváme opačným problémem: jak předvídat poptávku po produktech potraviny pro optimalizaci výrobních zakázek (také z vertikálních farem) a snížení odpad. Pro sezónnost a trendy použijeme Prophet by Meta, pro vzory LightGBM pokročilé tabulky a vytvoříme kompletní předpovědní kanál s funkcemi specifické inženýrství pro italský maloobchod s potravinami (vlivy počasí, svátky, akce).
Pokračovat s: Prognóza poptávky pro maloobchod s potravinami s Prophet a LightGBM
Související články v jiných sériích
- Řada IoT Pipeline: IoT Pipeline pro přesné zemědělství - MQTT, InfluxDB, Grafana pro monitorování venkovních senzorů
- Řada MLOps: MLOps pro modely AI ve výrobě – Jak nasadit model optimalizace receptury RL je ve výrobě
- Řada Computer Vision: CV pro řízení kvality - techniky umělé vidění pro hodnocení zralosti a kvality plodin
- Data & AI Business Series: AI ve výrobě – prediktivní Údržba a digitální dvojče, dvojité architektury použitelné pro vertikální zemědělství







