수직 농업 자동화: API를 통한 로봇 제어
밀라노 외곽 카베나고 디 브리안자(Cavenago di Brianza)의 버려진 산업 창고. 내부, 9,000미터 청적색 LED 조명 아래 양상추, 바질, 로켓이 자라는 정사각형 수직 선반 태양을 본 적도 없이. 온도는 21도, 습도는 70%, CO2는 1,200ppm으로 일정합니다. 모든 식물 맞춤형 조명 레시피를 갖추고 있으며, 각 재배 라인은 API를 통해 중앙 시스템과 통신합니다. 각 로봇은 실시간으로 어디로 가야 할지 알고 있습니다. 이것은 플래닛팜스, 이탈리아 수직 농장 2025년에는 자동화된 실내 농업에 대한 세계적 기준 중 하나가 되었습니다.
수직 농업은 더 이상 실험실 실험이 아닙니다. 2025년 글로벌 시장은 가치가 있다 96억 2천만 달러 2033년까지 392억 달러(CAGR 19.3%)로 예상됩니다. 차세대 수직 농장은 물 95% 감소 존경 전통 농업에서는 기후에 관계없이 일년 내내 생산하고 농약을 사용하지 않습니다. 야외에서보다 최대 100배 더 높은 평방미터당 수확량을 달성합니다. 그러나 이러한 결과는 이는 산업용 소프트웨어와 로봇 인프라를 통해서만 가능합니다.
이 기사에서는 환경 센서를 통해 수직 농장의 전체 소프트웨어 아키텍처를 구축합니다. Python의 PID 컨트롤러, FastAPI를 사용한 REST API 설계부터 로봇용 ROS2 통합까지, 디지털 트윈부터 강화학습 기반 최적화 파이프라인까지. 작업 코드, 프로덕션에서 테스트된 아키텍처, 이탈리아 생태계의 실수입니다.
이 기사에서 배울 내용
- 수직 농장을 위한 완벽한 소프트웨어 아키텍처: 센서, 컨트롤러, SCADA, 클라우드, AI
- LED 스펙트럼 관리: PAR, DLI, 양상추, 바질, 딸기에 대한 조명 레시피
- 온도, CO2, 습도 및 관개를 위해 Python으로 PID 컨트롤러 구현
- 작물 레시피 관리 및 액추에이터 제어를 위한 FastAPI를 사용한 REST API 설계
- 자동화된 로봇 파종, 이식 및 수확을 위한 ROS2 통합
- 농장의 디지털 트윈: 식물 성장 시뮬레이션 및 레이아웃 최적화
- 조명 레시피 최적화를 위한 강화 학습
- IoT 인프라: Modbus RTU, MQTT, OPC-UA, 산업용 게이트웨이
- 경제성 분석: CAPEX, OPEX, 손익분기점 및 야외 농업과의 비교
- 사례 연구: 이탈리아의 선도적인 수직 농장인 Planet Farms 및 Agricola Moderna
FoodTech 시리즈 - 모든 기사
| # | Articolo | 수준 | 상태 |
|---|---|---|---|
| 1 | Python 및 MQTT를 사용한 정밀 농업용 IoT 파이프라인 | 고급의 | 게시됨 |
| 2 | 작물 모니터링을 위한 ML Edge: 현장의 컴퓨터 비전 | 고급의 | 게시됨 |
| 3 | 위성 API 및 식생 지수: Python 및 Sentinel-2를 사용한 NDVI | 중급 | 게시됨 |
| 4 | 식품의 블록체인 추적성: 현장에서 슈퍼마켓까지 | 중급 | 게시됨 |
| 5 | 식품 산업의 품질 관리를 위한 컴퓨터 비전 | 고급의 | 게시됨 |
| 6 | FSMA 및 디지털 규정 준수: 규제 프로세스 자동화 | 중급 | 게시됨 |
| 7 | 수직 농업 자동화: API를 통한 로봇 제어(현재 위치) | 고급의 | 현재의 |
| 8 | Prophet 및 LightGBM을 사용한 식품 소매 수요 예측 | 중급 | 곧 출시 예정 |
| 9 | Farm Intelligence 대시보드: Grafana를 사용한 실시간 분석 | 중급 | 곧 출시 예정 |
| 10 | 공급망 식품 최적화: 폐기물 감소를 위한 ML | 중급 | 곧 출시 예정 |
2025년 수직 농업 시장: 성장 및 기술 동인
수직 농업은 2023~2024년에 일부 대규모 플레이어와 함께 통합 단계를 거쳤습니다. 부담을 안고 파산을 선언한 북미 기업(AeroFarms, AppHarvest, Bowery Farming) CAPEX가 매우 높고 에너지 비용이 폭발적으로 증가합니다. 하지만 시장은 멈추지 않았습니다. 구조가 재편되었습니다. 보다 효율적인 모델을 구축한 사람들에게 보상을 주는 다윈주의 선택을 통해 확장 전 견고한 단위 경제성.
2025년에 숫자는 성숙도가 높아진다는 것을 말해줍니다. 글로벌 시장은 그만한 가치가 있습니다. 96억 2천만 달러 2033년에는 392억 명으로 성장할 것입니다. 또 다른 추정치는 (Maximize Market Research)에서는 2025년에 이를 80억 달러로 예상하고 2025년에는 397억 달러로 예상합니다. 2032년에는 CAGR 25.7%로 성장할 것입니다. 추정치의 차이는 구성 요소를 분류하기가 어렵다는 점을 반영합니다. 정확히는 "수직 농업"(실내 쌓인 트레이만? 고급 온실도?)이라는 추세가 있습니다. 그리고 분명합니다. 연결된 농업용 로봇 시장은 2025년에 102억 3천만 달러 규모로 성장할 것입니다. 2030년까지 282억 명으로 성장
수직 농업과 전통 농업: 성과 비교
| 매개변수 | 야외 농업 | 온실 | 수직 농장 |
|---|---|---|---|
| 물 소비량(상대) | 100% | 30-40% | 5-10% |
| m²당 수확량(상추) | ~2kg/m²/년 | ~15kg/m²/년 | ~150-200kg/m²/년 |
| 생산주기/년 | 1-3 | 4-8 | 12-18 |
| 살충제 | 필요성이 높다 | 줄인 | Zero |
| 기후 의존성 | Totale | 부분 | 없음 |
| 토지 이용 (토지) | 1x | 1x | 0.01-0.05x |
| 필요한 에너지 | 낮은 | 평균 | 높음(LED + HVAC) |
| 생산비(상추) | $0.5-1/kg | $1.5-3/kg | $4-8/kg |
2025년 성장의 4가지 동인은 다음과 같습니다. 첫째, 고효율 LED 비용이 하락했습니다. 지난 10년 동안 70% 증가하여 조명 설비투자에 대한 접근성이 훨씬 높아졌습니다. 둘째, 나는 AI 기반 제어 시스템으로 최대 3~4년 동안 상상할 수 없는 에너지 최적화가 가능합니다. 전에. 셋째, 지역의 신선하고 무농약 제품에 대한 수요가 지속적으로 증가하고 있으며, 특히 도시 지역에서. 넷째, 파종, 이식, 수확을 위한 로봇모듈 이제 더 이상 실험실 가격이 아닌 산업 가격으로 구입할 수 있습니다.
수직 농장을 위한 소프트웨어 아키텍처: 완전한 스택
현대 수직 농장은 본질적으로 사이버-물리적 시스템입니다. 모든 물리적 결정(켜기) LED, 밸브 열기, 로봇 이동) 및 소프트웨어 계산 결과. 아키텍처 제어는 실시간이어야 하고, 작물 안전은 신뢰할 수 있어야 하며, 확장 가능해야 합니다. 수백 개의 재배 지역을 관리합니다.
엔드투엔드 아키텍처 스택
+-----------------------------------------------------------------------+
| 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) |
+-----------------------------------------------------------------------+
산업용 PLC(Siemens S7, Beckhoff)와 단일 보드 컴퓨터(Raspberry Pi 4, BeagleBone)은 필요한 신뢰성 수준에 따라 다릅니다. 수확량이 많은 상업용 농장의 경우 가치는 하드웨어 이중화와 올바른 선택을 갖춘 IEC 61131-3 인증 PLC입니다. 프로토타입 및 실험 농장, 임베디드 하드웨어의 Python 솔루션으로 더욱 유연하고 빠르게 개발할 수 있습니다.
환경 제어 시스템: LED, HVAC, CO2 및 관개
환경 제어는 수직 농장의 운영 핵심입니다. 4가지 매개변수가 지배적입니다. 빛의 스펙트럼과 강도, 공기 온도, CO2 농도 및 영양 용액의 구성. 각각에는 전용 제어 루프가 필요합니다.
LED 스펙트럼 관리: PAR, DLI 및 조명 레시피
식물은 가시광선을 모두 동일하게 사용하지 않습니다. 광합성 활성 범위 에서 간다 400~700nm (PAR - 광합성 활성 방사선). 내부 이 범위에서 파란색(400-500nm)은 잎의 형태와 화합물의 합성을 조절합니다. 방향족; 빨간색(600-700 nm)은 광합성의 주요 동인입니다. 원적외선(700-800nm) 피토크롬 시스템을 통해 꽃과 식물의 기하학에 영향을 미칩니다.
DLI(Daily Light Integral)는 식물이 받는 PAR 광자의 총량을 측정합니다. 24시간 동안 mol/m²/day로 표시됩니다. 그리고 레시피 크기 조정에 대한 가장 중요한 측정항목은 밝다. 2025년 Nature Scientific Reports에 발표된 연구에 따르면 최적화된 LED 수직 농업을 위해 그들은 최대로 생산합니다. 32% 더 많은 수율 스펙트럼에 비해 표준, 양상추와 바질의 신선한 무게가 더 큽니다.
주요 작물의 조명 매개변수
| 문화 | PPFD (μmol/m²/s) | DLI 목표(mol/m²/일) | 최적의 스펙트럼 | 광주기 |
|---|---|---|---|---|
| 상추(잎) | 150-250 | 오후 3~6시 | R:B = 4:1, +원적외선 5% | 16-18시간 빛 |
| 양상추(머리) | 200-300 | 17-22 | R:B = 3:1, UV 380nm 2% | 16시간 빛 |
| 바질 | 200-300 | 오후 2-5시 | R:B = 3:1, 파란색 20-25% | 16시간 빛 |
| 시금치 | 150-200 | 12-17 | R:B = 4:1 | 14-16시간 빛 |
| 딸기 (식물성) | 200-300 | 오후 3~8시 | R:B = 3:1 | 16시간 빛 |
| 딸기(꽃이 핀다) | 300-400 | 20-25 | R:B:FR = 3:1:0.5 | 12시간 빛 |
| 마이크로그린 | 100-200 | 8-12 | 풀 스펙트럼 화이트 | 16시간 빛 |
| 아로마 허브 | 200-250 | 14-16 | 파란색 15%, R 지배적 | 16시간 빛 |
LED 관리 컨트롤러는 이러한 레시피를 LED 드라이버에 대한 PWM 신호로 변환해야 합니다. 각 재배 지역마다 레시피가 다를 수 있으며, 레시피는 일년 내내 변경될 수 있습니다. 재배 주기(예: 바질의 향을 강화하기 위해 지난 48시간 동안 파란색을 더 많이 나타냄) 다음은 완전한 PID 컨트롤러의 Python 구현입니다.
"""
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,
)
관개 시스템: NFT, DWC 및 Aeroponics
수직 농장의 세 가지 지배적인 수경 기술에는 제어 아키텍처가 있습니다. 각각 모니터링하고 규제해야 하는 중요한 매개변수가 다릅니다.
수경재배 시스템의 비교
| 체계 | 이상적인 작물 | 중요한 매개변수 | 복잡성 제어 | 물 소비량 |
|---|---|---|---|---|
| NFT (영양막 기술) | 양상추, 허브 | 유속, 채널 기울기, EC, pH | 평균 | 최소(재순환) |
| DWC (심해수경) | 양상추, 시금치 | 산소화(DO), EC, pH, 온도 | 낮은 | 베이스 |
| 에어로포닉스 | 딸기, 뿌리, 허브 | 분무주기, 압력, EC, pH | 높은 | 최소(토양 대비 90%) |
| 기판 (코코넛/암면) | 토마토, 고추 | 주기 관개, EC, pH, 배수 | 평균 | 보통의 |
"""
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
로봇 공학 및 자동화: 수직 농장의 ROS2
로봇공학은 2025년 수직 농업에서 가장 변혁적인 요소입니다. 수동 작업 더 집중적인 작업으로는 파종(쟁반에 파종), 이식(묘목을 화분에 심기)이 있습니다. 최종 랙), 수확 및 포장. 작업자 한 명이 약 500~700그루의 식물을 이식할 수 있습니다. 지금은; 이식 로봇은 오류율이 낮고 시간당 2,000~3,000개의 식물을 처리합니다. 1%에서. 하루에 상추 30,000 트레이(Agnadello의 Agricola Moderna의 경우처럼), 로봇공학은 선택이 아니라 필수입니다.
ROS2(Robot Operating System 2)는 로봇 프로그래밍의 사실상 표준이 되었습니다. 실내 환경에서. ROS1과 비교하여 기본 실시간 지원(DDS 미들웨어)을 제공합니다. 향상된 보안 아키텍처, 기본 다중 로봇 지원 및 관리형 수명 주기 노드. 노드와 주제 구조를 통해 계획 논리를 명확하게 분리할 수 있습니다. 움직임, 운동 제어, 인공 시각 및 시스템과의 인터페이스 농장 관리의.
"""
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()
수직 농장을 위한 API 설계: FastAPI REST 백엔드
API 계층은 농장의 물리적 제어 시스템과 농장 간의 통합 지점입니다. 외부 세계: 기업 ERP, 고객 포털, 운영자 모바일 앱, WMS 시스템 유통창고. 이 맥락에서 잘못 설계된 API는 불일치를 초래합니다. 작물 조리법, 일정 오류 및 잠재적인 작물 손실. 좋은 것 반대로 API는 모든 하위 시스템을 조정하는 신경계입니다.
"""
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: 시뮬레이션 및 최적화
수직 농장의 디지털 트윈과 동작을 복제하는 계산 모델 예측 시뮬레이션이 가능하도록 물리학을 정확하게 농장화합니다. 아니요 예 시각적인 3D 복제(즉, "시각화")가 아니라 수학적 모델입니다. 현재 환경 변수의 상태를 고려하여 식물 성장을 예측하고 수확할 시간.
통제된 환경에서 가장 많이 사용되는 식물 성장 모델은 접근 방식을 기반으로 합니다. 방사선 이용효율(RUE): 축적된 바이오매스는 다음에 비례한다. 온도, CO2, 물과 영양 가용성. 원래 시스템용으로 개발된 이 모델은 개방형 필드(예: DSSAT, APSIM)의 수확량 예측이 환경에 맞게 조정되었습니다. 실험적으로 보정된 매개변수를 사용하는 실내.
"""
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)
최적화를 위한 AI: Bright Recipe를 위한 강화 학습
디지털 트윈은 단순한 예측보다 더 강력한 것을 가능하게 합니다. 레시피 최적화 강화학습(RL)을 통해 에이전트 RL (실제 농장이 아닌) 디지털 트윈과 상호 작용하고 수천 가지 조합을 탐색합니다. 가벼운 매개변수를 사용하여 소비를 최소화하면서 수율을 최대화하는 구성을 찾습니다. 활력이 넘칩니다. 시뮬레이션에서 최적의 레시피가 발견되면 이를 한 번에 검증합니다. 대규모 배포 전 실제 농장의 파일럿 영역.
이 접근법은 말했다 시뮬레이션에서 실제로의 전송, 그리고 연구의 최전선 AI 수직농업에 시뮬레이션과 현실 사이의 격차(sim-to-real gap)는 농장에서 수집한 실제 데이터를 바탕으로 성장 모델을 지속적으로 보정합니다.
"""
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")
산업용 IoT 인프라: Modbus, MQTT, OPC-UA
수직 농장은 산업 프로토콜의 오버레이를 사용합니다: Modbus RTU/TCP 레거시 센서 및 액추에이터(온습도계, CO2 측정기, 컨트롤러)와의 통신 LED), Siemens/Beckhoff PLC 및 SCADA 시스템과의 통신을 위한 OPC-UA, MQTT 클라우드로 데이터를 전송합니다. 선택은 공급업체의 하드웨어, 대기 시간에 따라 다릅니다. 필수 및 보안 수준.
수직 농업의 IoT 프로토콜: 비교
| 규약 | 레이어 | 숨어 있음 | 안전 | 일반적인 사용 사례 |
|---|---|---|---|---|
| 모드버스 RTU | 현장-PLC | 10-100ms | 부재(기존) | EC/pH 센서, LED 드라이버 |
| 모드버스 TCP | PLC-SCADA | 5-50ms | 선택적 TLS | PLC 데이터 수집 |
| OPC-UA | PLC-SCADA-클라우드 | 1-50ms | X.509, 서명, 암호화 | 인더스트리 4.0 표준 |
| MQTT | 엣지 클라우드 | 10-500ms | TLS + 인증 | 클라우드에 대한 원격 측정 |
| REST/HTTP | 클라우드-클라우드 | 50-500ms | HTTPS, OAuth2 | ERP 통합 API |
"""
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)
수직 농업 경제학: CAPEX, OPEX 및 손익분기점
수직 농업은 경제적으로 지속 가능합니까? 2025년의 대답은 다음과 같습니다. 의존한다. 작물에 따라 다릅니다(허브와 소형 녹색 채소는 상추보다 수익성이 훨씬 더 높음). 규모(규모의 경제는 5,000m² 이상으로 나타남), 위치(비용 지역 에너지 및 인건비가 중요함) 및 판매 채널별(직접 판매) 프리미엄 가격의 B2C 대 GDO 원자재).
경제성 분석: 순 경작 면적 1,000m²
| 목소리 | Valore | 메모 |
|---|---|---|
| CAPEX(초기투자) | ||
| 구조 및 시스템 | €800,000 | 창고 개조 |
| NFT 선반 및 채널 | €300,000 | 수경재배 시스템 |
| LED 조명 | €600,000 | 600W/m² 효율 2.8μmol/J |
| HVAC 및 기후 | €250,000 | 냉방+제습 |
| CO2 시스템 | €50,000 | 보관+유통 |
| 자동화 및 로봇공학 | €400,000 | 파종기, 이식기, 수확기 |
| 소프트웨어 및 통합 | €150,000 | SCADA, API, 디지털 트윈 |
| 총 CAPEX | €2,550,000 | ~€2,550/m² |
| 연간 OPEX | ||
| 전기 | €420,000 | 35% OPEX - 주요 임계 에너지 |
| 작업(10명의 운영자) | €350,000 | 운영비용 27% |
| 종자 및 기질 | €80,000 | 6% 운영비용 |
| 영양소와 CO2 | €60,000 | 5% 운영비용 |
| 유지 | €90,000 | 7% 운영비용 |
| 포장 및 물류 | €120,000 | 9% 운영비용 |
| 기타(보험 등) | €80,000 | 6% 운영비용 |
| 총 OPEX | €1,200,000 | €1,200/m²/년 |
| 수익 | ||
| 상추 생산 (18주기 x 25kg/m²) | 450kg/m²/년 | 총 450,000kg |
| 프리미엄 GDO 판매 가격 | €3.5/kg | 대 실외 €0.8-1.2 |
| 총 수익 | €1,575,000 | |
| 에비타 | €375,000 | 마진 23.8% |
| CAPEX 상각비(10년) | €255,000 | |
| 세전 순이익 | €120,000 | 마진 7.6% |
| 손익분기점(년) | ~8~10년 | 허브 포함: 4~5년 |
수직 농업의 에너지 문제
수직 농업의 에너지와 실존적 도전. 2025년에 출시될 가장 효율적인 LED 그들은 대략 도달한다 3.0-3.5μmol/J 광자 효율성. 생산하려면 16시간의 광주기를 갖춘 17mol/m²/day의 DLI에는 약 280Wh/m²/day가 필요합니다. 102kWh/m²/년 그냥 조명용. 1,000m²의 면적과 비용 €0.15/kWh(산업 관세 이탈리아 2025), LED 요금은 이미 €153,000/년입니다. HVAC(일반적으로 LED 에너지의 60-70%), CO2, 펌프, 자동화를 추가하세요. 연간 €420,000로 쉽게. 저비용 재생 에너지(예: 농업)에 접근할 수 있는 사람 100% 재생 가능 에너지를 사용하는 현대식) 또는 태양광 발전 커버를 상당 부분 설치했습니다. 필요하지만 전부는 아닙니다.
수직 농업 모든 에너지 가격에서는 지속 가능하지 않습니다. 에너지로 €0.25/kWh 이상(2022~2023년과 같은 위기 기간에 가능한 시나리오), 다양한 경제 모델 그들은 무너진다. LED 효율이 지속적으로 향상되고 재생 에너지가 증가할 것으로 예상됩니다. 비용이 점점 더 적게 듭니다.
사례 연구: 행성 농장과 현대 농업 - 이탈리아 모델
이탈리아는 유럽에서 가장 발전된 수직 농업 프로젝트 중 두 곳의 본고장입니다. 밀라노 배후지에 기반을 두고 있습니다. 그들의 경로는 다르지만 상호 보완적이며 다음을 나타냅니다. 유럽 수직 농업의 확장성과 수익성 문제에 대한 두 가지 접근 방식.
플래닛 팜스 - 카베나고 디 브리안자
2018년 Luca Travaglini와 Massimiliano Loschi가 설립한 Planet Farms는 카베나고(MB)의 이전 산업 지역에 있는 첫 번째 공장. 원래 식물은 9,000m² 규모로 2024년 유럽 최대 규모 중 하나가 되었습니다. 20,000m² 성장 표면. 2023년 11월, Planet Farms는 라운드를 올렸습니다. 5억 달러 가치로 4천만 달러로, 해당 분야에서 유럽 최대 규모 중 하나입니다. 2025년 Swiss Life Asset Managers와의 파트너십을 통해 JV가 탄생했습니다. €200 수백만 유럽 전역에 대규모 수직 농장을 개발합니다.
Siemens와의 기술 파트너십은 자동화의 핵심입니다: 제어 시스템 산업용 Siemens S7-1500은 환경 루프를 관리하고 플랫폼은 Mindsphere(현 Siemens Industrial Copilot)는 데이터를 수집하고 분석합니다. 제품 주력 제품은 "리빙 허브"입니다. 향기로운 허브는 트레이에 담겨 판매됩니다. 상추보다 마진이 더 높은 루트, 프리미엄 세그먼트입니다.
현대 농업 - Agnadello(CR)
Agricola Moderna는 2018년 Pierluigi Giuliani와 Benjamin Franchetti가 밀라노에서 설립한 브랜드입니다. 2024년 9월 신공장 오픈 11,000m² 광고 Agnadello (Cremona)는 Intesa Sanpaolo로부터 1천만 유로의 대출을 받았습니다. 공장은 생산 하루에 샐러드 30,000봉지, 전원 공급 100% 재생 가능 에너지원에서 추출되며, R&D 팀이 개발한 내부 AI를 통해 레시피를 최적화하세요.
Agricola Moderna와 초분광 이미징 시스템의 기술적 차별화 (Spemim과의 파트너십) 식물의 영양 상태를 평가할 수 있습니다. 증상이 사람의 눈에 보이기 전에. 재구성용 RGB-D 카메라 캐노피의 3D, 수천 개의 측정 지점을 갖춘 환경 센서 및 알고리즘 내부적으로 개발된 컴퓨터 비전 시스템은 독점 기술 스택을 구성합니다.
플래닛 팜과 현대 아그리콜라 비교
| 매개변수 | 플래닛팜스 | 현대 농업 |
|---|---|---|
| 표면 | 20,000m² | 11,000m² |
| 생산/일 | N.D.(허브 포커스) | 샐러드백 30,000개 |
| 자금 조달 | $40M 라운드 + €200M JV | €10M 인테사 산파올로 |
| PLC/자동화 | 지멘스 S7-1500 | Beckhoff + 커스텀 |
| AI 플랫폼 | 지멘스 산업 부조종사 | 사내 개발 |
| 컴퓨터 비전 | RGB 표준 | 초분광(Specim) |
| 에너지 | 부분적으로 재생 가능 | 100% 재생 가능 |
| 목표 시장 | 프리미엄 소매 GDO + 영국 확장 | 이탈리아 대규모 소매업(IV 제품군) |
해당 부문의 과제, 한계 및 현실
수직 농업에 대한 정직한 기사에는 이점뿐만 아니라 실제 과제도 포함되어야 합니다. 해당 부문은 2022~2024년에 파산과 함께 주요 환멸 국면을 경험했습니다. 수십억 달러의 투자를 불태운 놀라운 일들입니다. 이유를 이해하는 것이 기본입니다 지속가능한 시스템을 구축합니다.
수직 농업의 실제 과제
- 에너지 비용: 그리고 최고의 살인자. 수직 농장 운영자의 60% 주로 전기 비용으로 인해 아직 수익성이 없습니다. 에너지가 없으면 저비용 재생 가능 경제 모델은 프리미엄 작물에만 적용됩니다.
- 제한된 작물 품종: 수직농업은 탁월하다 잎채소(상추, 시금치), 아로마 허브, 마이크로그린. 토마토, 고추의 경우, 오이의 에너지 생산량은 아직 경쟁력이 없습니다. 곡물, 콩과 식물 및 뿌리는 기술적, 경제적 접근이 불가능합니다.
- 높은 자본 지출: 전문적인 설치 비용은 2,000-4,000유로입니다. 재배 면적의 m²당. 상추의 손익 분기점은 8~10년입니다. 많은 투자자들에게는 너무 많은 것입니다. 허브와 마이크로그린만이 4~5년 안에 손익분기점에 도달합니다.
- LED 공급망 종속성: 고품질 LED 칩은 다음과 같습니다. 몇몇 공급업체(Epistar, Lumileds, Osram)에서 생산됩니다. 공급망 중단 초기 CAPEX와 유지 관리 모두에 영향을 미칩니다.
- 실제 지속 가능성과 인지된 지속 가능성: 물 절약(95%)은 현실입니다. 그러나 탄소 발자국은 에너지 혼합에 따라 크게 달라집니다. 전력이 공급되는 수직 농장 석탄은 야외 농업보다 탄소 배출량이 더 나쁩니다. 재생에너지로만 가능 균형은 확실히 긍정적이 됩니다.
- 기술적 확장성: 500m² 규모의 시범 농장에서 2만㎡의 상업공간은 단순한 곱셈이 아니다. 제어 시스템, 작물 관리, 내부 물류 및 로봇 시스템이 필요합니다. 모든 규모의 점프에서 심층적인 재설계.
프로덕션 배포: 컨테이너화 및 모니터링
수직 농장의 소프트웨어 구성 요소는 실시간 PID 컨트롤러(실행 PLC 또는 컨테이너화되지 않은 Raspberry Pi에서 직접) FastAPI 백엔드 및 AI 파이프라인(Docker 컨테이너에서 편안하게 생활) 일반적인 클라우드 인프라 온프레미스 엣지 레이어와 클라우드 제어 플레인을 결합합니다.
# 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:
수직 농업의 동향과 혁신 (2025-2026년)
이 부문은 에너지 효율성, 확장이라는 세 가지 측면에서 빠르게 발전하고 있습니다. 농작물, 그리고 더욱 심층적인 AI 통합. 가장 중요한 추세는 다음과 같습니다.
2025년 수직 농업의 주요 혁신
| 혁신 | 영향 | 상업상태 |
|---|---|---|
| LED 효율 4.0+ µmol/J | -20-25% 조명 에너지 비용 | 있음(LG, 시그니파이) |
| 초분광 이미징 | 식물 스트레스 조기진단, 최적화 | 조기 채택(Specim + Agricola M.) |
| 동적 레시피를 위한 RL | 동일한 에너지로 수율 +15-25% | 고급 검색/초기 프로덕션 |
| 고속파종로봇 | 시간당 3,000개 이상의 파종 대 700개 수동 | 상업용(대량, 80에이커) |
| 버섯/버섯재배 | 작물 믹스 다양화, 높은 수익성 | 상업적 성장 |
| 데이터 센터 폐열로 활용되는 수직 농장 | HVAC 감소를 위한 열 회수 | 파일럿(핀란드, 독일) |
| BESS와의 태양광 통합 | -최적 시나리오에서 40-60% 에너지 비용 | 성장 |
특히 흥미로운 새로운 추세는 수직 농업과 수직 농업의 통합입니다. 데이터 센터: 서버는 회수할 수 있는 폐열을 생성하여 비용을 절감합니다. 추운 달에 온실을 난방하는 데 드는 비용. 에너지가 넘치는 핀란드에서 비용이 많이 들기 때문에 여러 파일럿 프로젝트에서 이러한 공생을 테스트하고 있습니다. 폐열 40~60°C에서 겨울 온실 재배 온도 유지에 이상적 추가적인 에너지 소비 없이.
결론: 소프트웨어 엔지니어링 프로젝트로서의 수직 농업
현대 수직 농장과 무엇보다도 소프트웨어 시스템입니다. 하드웨어(LED, 센서, 로봇, 선반)은 필요하지만 충분하지는 않습니다. 변화시키는 것은 소프트웨어 수준입니다. 최적화된 식품 공장의 조명 창고. PID 컨트롤러가 관리 과학적으로 보정된 레시피에 따른 환경; REST API가 팜을 연결합니다. 상업 생태계에; ROS2는 로봇을 조정합니다. 디지털 트윈으로 시뮬레이션 가능 예측; 강화 학습은 설계된 것보다 더 나은 레시피를 찾습니다. 농업 경제학자가 수동으로.
수직농업 시장은 2025년 96억에서 2033년 390억으로 자동 보장: 업계의 문제 해결 능력에 따라 다름 에너지를 절약하고 경제적으로 생산할 수 있는 작물의 범위를 확대합니다. 지속 가능합니다. Planet Farms와 Agricola Moderna가 있는 이탈리아는 올바른 위치에 있습니다. 특히 PNRR과 인센티브 이후 이러한 전환의 주인공이 됩니다. 기술적 CAPEX 비용을 절감하는 5.0 전환.
이 분야에 진출하려는 개발자에게 가장 많이 요구되는 기술은 다음과 같습니다. 자동화 및 ML을 위한 Python, 산업용 백엔드를 위한 FastAPI, 로봇공학을 위한 ROS2, 산업용 프로토콜용 MQTT/Modbus/OPC-UA, 시계열용 InfluxDB 및 기본 사항 최적화할 매개변수를 이해하기 위한 식물 생리학. 방법을 알 필요는 없습니다. 정원 가꾸기, 식물이 특정 파장에서 더 많이 자라는 이유 이해 이는 좋은 제어 시스템과 훌륭한 제어 시스템의 차이를 만듭니다.
자세히 알아보기 위한 도구 및 리소스
- 파이모드부스 - PLC 및 센서와의 통신에 이상적인 Modbus RTU/TCP용 Python 라이브러리
- 파호-MQTT - 브로커와의 통합을 위한 MQTT Python 클라이언트(EMQX, HiveMQ, Mosquitto)
- ROS2 험블/재지 - 로봇공학 프레임워크, docs.ros.org의 공식 문서
- FastAPI - 산업용 제어 API에 완벽한 Python 비동기 백엔드
- 인플럭스DB 2.x - 센서 분석을 위한 Flux 쿼리 언어가 포함된 시계열 데이터베이스
- 안정적인 기준선3 - 레시피 최적화를 위한 RL 구현(PPO, SAC, TD3)
- 연무장 - RL 환경 표준, 디지털 트윈 환경에 사용
- 점화 SCADA - 저렴한 OEM 라이센스 산업용 SCADA 플랫폼
- 수직 농장 일일 - 수직 부문에 대한 주요 뉴스 소스
- USDA CEA 지침 - 통제된 환경 농업을 위한 참조 매개변수
다음 시리즈: 식품 소매 수요 예측
8조에서는 반대 문제인 제품 수요를 예측하는 방법을 다룹니다. 생산 주문을 최적화하고(수직 농장에서도) 식품을 폐기물. 계절성과 추세에는 Prophet by Meta를, 패턴에는 LightGBM을 사용합니다. 고급 테이블 형식을 사용하여 기능을 갖춘 완전한 예측 파이프라인을 구축할 것입니다. 이탈리아 식품 소매를 위한 특정 엔지니어링(날씨 효과, 휴일, 프로모션).
계속: Prophet 및 LightGBM을 사용한 식품 소매 수요 예측
다른 시리즈의 관련 기사
- IoT 파이프라인 시리즈: 정밀 농업을 위한 IoT 파이프라인 - 실외 센서 모니터링을 위한 MQTT, InfluxDB, Grafana
- MLOps 시리즈: 프로덕션 AI 모델용 MLOps - 배포 방법 RL 레시피 최적화 모델이 생산 중입니다.
- 컴퓨터 비전 시리즈: 품질관리 이력서 - 기술 작물의 성숙도와 품질을 평가하는 인공 시각
- 데이터 및 AI 비즈니스 시리즈: 제조 분야의 AI - 예측 수직 농업에 적용 가능한 유지 관리 및 디지털 트윈, 트윈 아키텍처







