You dont have javascript enabled! Please enable it!

Caso práctico: Invernadero Modbus RS485 con Raspberry Pi 3

Caso práctico: Invernadero Modbus RS485 con Raspberry Pi 3 — hero

Objetivo y caso de uso

Qué construirás: Un controlador de invernadero utilizando Raspberry Pi 3 B+, Waveshare RS485 HAT y sensor Bosch BME680 para monitoreo y automatización ambiental.

Para qué sirve

  • Monitoreo de temperatura y humedad en tiempo real utilizando el BME680.
  • Control de sistemas de riego automatizados mediante comandos Modbus RS485.
  • Integración con plataformas IoT a través de MQTT para visualización remota.
  • Gestión de alertas por condiciones ambientales críticas (temperaturas extremas).

Resultado esperado

  • Latencia de respuesta del sistema de riego menor a 200 ms.
  • Monitoreo de datos ambientales con una frecuencia de 1 Hz.
  • Envío de datos a la nube con un throughput de 5 paquetes/s.
  • Alertas generadas en tiempo real en caso de condiciones fuera de rango.

Público objetivo: Ingenieros y desarrolladores de sistemas embebidos; Nivel: Avanzado

Arquitectura/flujo: Comunicación entre Raspberry Pi y dispositivos RS485 mediante el HAT de Waveshare, utilizando el bus I2C para el sensor BME680.

Nivel: Avanzado

Prerrequisitos

  • Sistema operativo:
  • Raspberry Pi OS Bookworm 64‑bit (release 2024-10-22 o posterior)
  • Kernel Linux 6.x incluido en la imagen oficial (no es necesario fijar versión exacta)
  • Toolchain y versiones exactas:
  • Python 3.11 (intérprete del sistema)
  • Módulo de entornos virtuales: python3.11-venv (APT)
  • pip 24.2 (actualizado dentro del venv)
  • Paquetes APT:
  • i2c-tools 4.3-2
  • python3-gpiozero 1.6.2-1
  • python3-smbus 5.1-1 (opcional si no usas smbus2 vía pip)
  • Paquetes pip (versiones fijadas en el entorno virtual):
  • pyserial==3.5
  • minimalmodbus==2.1.1
  • smbus2==0.5.0
  • bme680==1.1.1
  • gpiozero==1.6.2
  • typer==0.12.5 (CLI opcional clara)
  • rich==13.9.3 (logging legible opcional)
  • Acceso y permisos:
  • Usuario perteneciente a los grupos dialout e i2c
  • Acceso SSH o consola local

Notas importantes de compatibilidad:
– Para el control RS485 half‑duplex del SP3485 del HAT de Waveshare utilizaremos la línea RTS (GPIO17) como señal DE/RE. La conmutación de dirección se hará en usuario con pyserial.rs485.RS485Settings.
– El BME680 se usará por I2C en el bus 1 (SDA GPIO2, SCL GPIO3).

Materiales

  • Computadora principal:
  • Raspberry Pi 3 Model B+ (exactamente este modelo)
  • Interfaces y sensores:
  • Waveshare RS485 HAT (SP3485)
  • Bosch BME680 (breakout I2C 3.3 V)
  • Otros:
  • Fuente 5 V/2.5 A para Raspberry Pi 3 Model B+
  • Cables Dupont macho‑hembra para el BME680 (SDA/SCL/3V3/GND)
  • Destornillador pequeño para borneras del RS485 HAT
  • Resistencias de terminación si no vienen en el HAT (el HAT suele integrar jumper de 120 Ω)
  • Un módulo/esclavo Modbus RTU real en bus RS485 (por ejemplo, un relé Modbus de 4 canales o un módulo de 8 relés, ID=1), y opcionalmente un módulo de entradas analógicas (ID=2) para simular humedad de suelo
  • PC o la misma Raspberry con cable de red/wi‑fi para instalar paquetes

Objetivo del proyecto:
– Proyecto “rs485-modbus-greenhouse-control”: el Pi mide T/RH/Presión/Calidad de aire con el BME680 y gobierna actuadores en bus RS485‑Modbus (ventilación, riego, calefacción, nebulización) según consignas.

Preparación y conexión

1) Habilitar interfaces en Raspberry Pi OS Bookworm (64‑bit)

Opción A: usando raspi-config (interactivo):
– sudo raspi-config
– Interface Options:
– I2C: Enable
– Serial Port:
– Login shell over serial? No
– Enable serial interface? Yes
– Finish y reboot

Opción B: edición directa de /boot/firmware/config.txt:
– Edita el fichero:
– sudo nano /boot/firmware/config.txt
– Añade/asegura estas líneas (al final del archivo, una por línea):
– enable_uart=1
– dtoverlay=pi3-disable-bt
– dtparam=i2c_arm=on
– dtoverlay=uart0,ctsrts=on
– Guarda y sal; desactiva servicios que usen la UART:
– sudo systemctl disable –now hciuart.service || true
– Asegura pertenencia a grupos:
– sudo usermod -aG dialout,i2c $USER
– Reinicia:
– sudo reboot

Tras el reinicio, comprueba:
– ls -l /dev/serial0 → debe apuntar a /dev/ttyAMA0 en Pi 3 B+ con Bluetooth deshabilitado (PL011 estable)
– i2cdetect -y 1 → debe mostrar 0x76 o 0x77 (BME680)

2) Jumper/DIP del Waveshare RS485 HAT (SP3485)

  • Terminación 120 Ω: habilita el jumper de terminación si el HAT es el único en el extremo del bus (ON).
  • Polarización (bias): si tu bus no la provee, habilita los jumpers de pull‑up/pull‑down (si los trae).
  • Control de dirección: sitúa el jumper DE/RE en la posición “RTS” (esto conecta DE/RE del SP3485 a la línea RTS del PL011, que en el Pi está en GPIO17).
  • UART: el HAT usa GPIO14 (TXD) y GPIO15 (RXD) automáticamente al enchufarlo sobre el conector de 40 pines.

Conexión del bus:
– Bornera A(+) y B(−) del HAT a A/B de tu red RS485 de invernadero.
– GND de referencia: conecta GND si la topología lo requiere (recomendado para distancias largas o fuentes distintas).

3) Cableado del Bosch BME680 (I2C)

Conecta el módulo BME680 a la cabecera del Pi (niveles a 3.3 V):
– VCC del BME680 → 3V3 (Pin físico 1)
– GND del BME680 → GND (Pin físico 6 o 9, etc.)
– SDA del BME680 → GPIO2/SDA1 (Pin físico 3)
– SCL del BME680 → GPIO3/SCL1 (Pin físico 5)
– Dirección I2C: por defecto suele ser 0x76. Si el pin SDO está a VCC, será 0x77.

4) Mapa de pines/puertos

Función Pin GPIO Pin físico Interfaz Observaciones
UART TXD → SP3485 DI GPIO14 8 /dev/serial0 TX hacia bus RS485 via HAT
UART RXD ← SP3485 RO GPIO15 10 /dev/serial0 RX desde bus RS485 via HAT
RTS → SP3485 DE/RE GPIO17 11 RTS (PL011) Control de dirección (half‑duplex)
I2C SDA GPIO2 3 I2C bus 1 BME680 SDA
I2C SCL GPIO3 5 I2C bus 1 BME680 SCL
3V3 1 Alimentación BME680 VCC
GND 6 Tierra Común BME680 y HAT

Comprobaciones rápidas:
– i2cdetect -y 1 → 0x76 u 0x77
– raspi-gpio get 17 → confirmará que el pin existe (el estado cambiará dinámicamente durante transmisión)

Código completo

A continuación se presenta un script Python 3.11 completo para “rs485-modbus-greenhouse-control”. Integra:
– Lectura periódica de BME680 por I2C (temperatura, humedad, presión y gas).
– Control de actuadores Modbus RTU en bus RS485:
– Esclavo 1 (ID=1): Módulo de relés (coils) para ventilación, bomba de riego, calefacción, nebulización.
– Esclavo 2 (ID=2, opcional): Módulo de registros (holding registers) para humedad de suelo y nivel de tanque.

El control implementa una lógica simple con histéresis basada en consignas definidas por CLI. Se usa minimalmodbus con pyserial.rs485 para conmutar la dirección vía RTS (GPIO17).

Mapa lógico de Modbus utilizado

  • Esclavo 1 (ID=1), módulo relés:
  • Coil 0: Ventilación (fan)
  • Coil 1: Bomba de riego (pump)
  • Coil 2: Calefacción (heater)
  • Coil 3: Nebulización (mister)

  • Esclavo 2 (ID=2), módulo sensores (opcional):

  • Holding Register 0 (HR0): Humedad de suelo (%) escalada x10 (p.ej., 735 = 73.5%)
  • Holding Register 1 (HR1): Nivel de tanque (%) escalada x10

Ajusta estos índices a tu hardware real si difieren.

Script principal: rs485_modbus_greenhouse.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import time
import math
import logging
from dataclasses import dataclass
from typing import Optional, Tuple

import serial
from serial.rs485 import RS485Settings
import minimalmodbus
import bme680
from smbus2 import SMBus
import typer
from rich.logging import RichHandler

# -------------------------------------------
# Configuración de logging
# -------------------------------------------
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s | %(levelname)-8s | %(message)s",
    datefmt="[%H:%M:%S]",
    handlers=[RichHandler(rich_tracebacks=True)]
)
log = logging.getLogger("greenhouse")

# -------------------------------------------
# Dataclasses de consignas y estado
# -------------------------------------------
@dataclass
class Setpoints:
    temp_target_c: float = 26.0
    rh_target_pct: float = 65.0
    vpd_target_kpa: float = 1.0
    hyst_temp_c: float = 1.0
    hyst_rh_pct: float = 5.0
    max_co2_gas_index: int = 200  # pseudoindicador basado en gas resistance

@dataclass
class Actuators:
    fan: bool = False
    pump: bool = False
    heater: bool = False
    mister: bool = False

@dataclass
class Measurements:
    temp_c: float = float("nan")
    rh_pct: float = float("nan")
    pressure_hpa: float = float("nan")
    gas_ohm: float = float("nan")
    vpd_kpa: float = float("nan")
    soil_rh_pct: Optional[float] = None
    tank_level_pct: Optional[float] = None

# -------------------------------------------
# Utilidades: VPD y heurística IAQ simplificada
# -------------------------------------------
def saturation_vapor_pressure_kpa(t_c: float) -> float:
    # Fórmula de Tetens
    return 0.61078 * math.exp((17.27 * t_c) / (t_c + 237.3))

def vpd_kpa(t_c: float, rh_pct: float) -> float:
    svp = saturation_vapor_pressure_kpa(t_c)
    avp = svp * (rh_pct / 100.0)
    return max(0.0, svp - avp)

def gas_to_index(gas_ohm: float) -> int:
    # Heurística simple (no es BSEC IAQ). Escala resistencia a 0-500 aprox.
    if gas_ohm <= 0:
        return 500
    base = max(1.0, min(gas_ohm, 1e6))
    idx = int(500 - 100 * math.log10(base))
    return max(0, min(idx, 500))

# -------------------------------------------
# Inicialización BME680
# -------------------------------------------
def init_bme680(address: int = 0x76, i2c_bus: int = 1) -> bme680.BME680:
    sensor = bme680.BME680(address=address, i2c_device=SMBus(i2c_bus))
    sensor.set_humidity_oversample(bme680.OS_2X)
    sensor.set_pressure_oversample(bme680.OS_4X)
    sensor.set_temperature_oversample(bme680.OS_8X)
    sensor.set_filter(bme680.FILTER_SIZE_3)
    sensor.set_gas_status(bme680.ENABLE_GAS_MEAS)
    sensor.set_gas_heater_temperature(320)
    sensor.set_gas_heater_duration(150)
    sensor.select_gas_heater_profile(0)
    return sensor

def read_bme680(sensor: bme680.BME680) -> Tuple[float, float, float, float]:
    if sensor.get_sensor_data():
        t = sensor.data.temperature
        rh = sensor.data.humidity
        p = sensor.data.pressure
        gas = float(sensor.data.gas_resistance) if sensor.data.heat_stable else float('nan')
        return t, rh, p, gas
    return float('nan'), float('nan'), float('nan'), float('nan')

# -------------------------------------------
# Inicialización Modbus RTU (RS485 via RTS)
# -------------------------------------------
def make_modbus_instrument(port: str, slave_id: int, baud: int, parity: str, timeout: float) -> minimalmodbus.Instrument:
    instr = minimalmodbus.Instrument(port, slave_id)
    instr.serial.baudrate = baud
    instr.serial.bytesize = 8
    instr.serial.parity = {'N': serial.PARITY_NONE, 'E': serial.PARITY_EVEN, 'O': serial.PARITY_ODD}[parity.upper()]
    instr.serial.stopbits = 1
    instr.serial.timeout = timeout
    # Conmutación RS485 en RTS (GPIO17) con SP3485: True=TX, False=RX
    instr.serial.rs485_mode = RS485Settings(
        rts_level_for_tx=True,
        rts_level_for_rx=False,
        loopback=False,
        delay_before_tx=0.0,
        delay_before_rx=0.0001
    )
    instr.mode = minimalmodbus.MODE_RTU
    # Evitar eco local en algunos adaptadores
    instr.clear_buffers_before_each_transaction = True
    return instr

# -------------------------------------------
# Operaciones Modbus de alto nivel
# -------------------------------------------
def write_coil(instr: minimalmodbus.Instrument, coil_addr: int, value: bool, retries: int = 3) -> bool:
    for _ in range(retries):
        try:
            instr.write_bit(coil_addr, int(value), functioncode=5)  # FC5 write single coil
            return True
        except Exception as e:
            log.warning(f"Error escribiendo coil {coil_addr}: {e}")
            time.sleep(0.05)
    return False

def read_hr(instr: minimalmodbus.Instrument, reg_addr: int, retries: int = 3) -> Optional[int]:
    for _ in range(retries):
        try:
            return instr.read_register(reg_addr, number_of_decimals=0, functioncode=3, signed=False)
        except Exception as e:
            log.warning(f"Error leyendo HR{reg_addr}: {e}")
            time.sleep(0.05)
    return None

# -------------------------------------------
# Lógica de control del invernadero
# -------------------------------------------
def control_logic(meas: Measurements, sp: Setpoints, state: Actuators) -> Actuators:
    new_state = Actuators(**vars(state))

    # Reglas de temperatura (calefacción / ventilación)
    if not math.isnan(meas.temp_c):
        if meas.temp_c > sp.temp_target_c + sp.hyst_temp_c:
            new_state.heater = False
            new_state.fan = True
        elif meas.temp_c < sp.temp_target_c - sp.hyst_temp_c:
            new_state.heater = True
            new_state.fan = False

    # Reglas de humedad relativa / VPD (nebulización / ventilación)
    if not math.isnan(meas.rh_pct) and not math.isnan(meas.vpd_kpa):
        if meas.rh_pct < sp.rh_target_pct - sp.hyst_rh_pct or meas.vpd_kpa > sp.vpd_target_kpa + 0.1:
            new_state.mister = True
        elif meas.rh_pct > sp.rh_target_pct + sp.hyst_rh_pct or meas.vpd_kpa < sp.vpd_target_kpa - 0.1:
            new_state.mister = False

    # Gas/IAQ simple: si “índice de gas” alto (peor calidad), fuerza ventilación
    if not math.isnan(meas.gas_ohm):
        idx = gas_to_index(meas.gas_ohm)
        if idx > sp.max_co2_gas_index:
            new_state.fan = True

    # Reglas de riego por humedad de suelo (si está disponible)
    if meas.soil_rh_pct is not None:
        if meas.soil_rh_pct < 35.0:
            new_state.pump = True
        elif meas.soil_rh_pct > 45.0:
            new_state.pump = False

    return new_state

# -------------------------------------------
# Aplicación principal
# -------------------------------------------
def main(
    port: str = typer.Option("/dev/serial0", help="Puerto serie (UART) del RS485 HAT"),
    baud: int = typer.Option(19200, help="Baudios Modbus RTU"),
    parity: str = typer.Option("E", help="Paridad: N/E/O"),
    timeout: float = typer.Option(0.2, help="Timeout Modbus en segundos"),
    slave_relays: int = typer.Option(1, help="ID Modbus del módulo de relés"),
    slave_sensors: Optional[int] = typer.Option(2, help="ID Modbus del módulo de sensores (opcional)"),
    bme_addr: int = typer.Option(0x76, help="Dirección I2C del BME680 (0x76 o 0x77)"),
    i2c_bus: int = typer.Option(1, help="Bus I2C (1 por defecto)"),
    loop_period: float = typer.Option(2.0, help="Periodo de control (s)"),
    temp_target: float = typer.Option(26.0, help="Consigna de temperatura (°C)"),
    rh_target: float = typer.Option(65.0, help="Consigna de RH (%)"),
    vpd_target: float = typer.Option(1.0, help="Consigna de VPD (kPa)")
):
    log.info("Inicializando BME680…")
    sensor = init_bme680(address=bme_addr, i2c_bus=i2c_bus)
    # Warm-up gas
    log.info("Calentando sensor de gas (BME680) 30 s…")
    time.sleep(30)

    log.info(f"Preparando Modbus RTU en {port} @ {baud} {parity} 8E1 timeout={timeout}s")
    relays = make_modbus_instrument(port, slave_relays, baud, parity, timeout)
    sensors = None
    if slave_sensors is not None:
        sensors = make_modbus_instrument(port, slave_sensors, baud, parity, timeout)

    sp = Setpoints(temp_target_c=temp_target, rh_target_pct=rh_target, vpd_target_kpa=vpd_target)
    state = Actuators()

    last_apply = None

    while True:
        # Medición local
        t_c, rh, p_hpa, gas = read_bme680(sensor)
        my_vpd = vpd_kpa(t_c, rh) if (not math.isnan(t_c) and not math.isnan(rh)) else float("nan")

        meas = Measurements(
            temp_c=t_c,
            rh_pct=rh,
            pressure_hpa=p_hpa,
            gas_ohm=gas,
            vpd_kpa=my_vpd,
        )

        # Medición de sensores Modbus opcionales
        if sensors is not None:
            soil_raw = read_hr(sensors, 0)
            tank_raw = read_hr(sensors, 1)
            if soil_raw is not None:
                meas.soil_rh_pct = soil_raw / 10.0
            if tank_raw is not None:
                meas.tank_level_pct = tank_raw / 10.0

        # Lógica de control
        new_state = control_logic(meas, sp, state)

        # Aplicación en hardware (esclavo relés)
        if new_state != state or (last_apply is None) or (time.time() - last_apply > 10.0):
            ok0 = write_coil(relays, 0, new_state.fan)
            ok1 = write_coil(relays, 1, new_state.pump)
            ok2 = write_coil(relays, 2, new_state.heater)
            ok3 = write_coil(relays, 3, new_state.mister)
            last_apply = time.time()
            if not all([ok0, ok1, ok2, ok3]):
                log.warning("No se pudieron aplicar todos los estados de relés (revisa bus/ID/terminación).")
            state = new_state

        # Registro
        log.info(
            f"T={meas.temp_c:.2f}°C RH={meas.rh_pct:.1f}% VPD={meas.vpd_kpa:.2f}kPa "
            f"P={meas.pressure_hpa:.1f}hPa Gas={meas.gas_ohm if not math.isnan(meas.gas_ohm) else float('nan'):.0f}Ω "
            f"Soil={meas.soil_rh_pct if meas.soil_rh_pct is not None else float('nan'):.1f}% "
            f"Tank={meas.tank_level_pct if meas.tank_level_pct is not None else float('nan'):.1f}% | "
            f"Fan={int(state.fan)} Pump={int(state.pump)} Heater={int(state.heater)} Mister={int(state.mister)}"
        )

        time.sleep(loop_period)

if __name__ == "__main__":
    typer.run(main)

Puntos clave del código:
– RS485Settings en pyserial con rts_level_for_tx=True y rts_level_for_rx=False: esto hace que la línea RTS (GPIO17) conduzca DE=1 al transmitir y RE=0 al recibir, conmutando correctamente el SP3485 del HAT de Waveshare.
– Lógica de histéresis para evitar oscilaciones.
– Lectura de registros Modbus opcionales para humedad de suelo y nivel de tanque (si tienes un módulo de sensores).
– bme680: se ajustan oversamplings y el calentador de gas; se añade un warm‑up de 30 s.

Script auxiliar de verificación del BME680 (opcional)

#!/usr/bin/env python3
# bme680_check.py
import time
import bme680
from smbus2 import SMBus

def main(addr=0x76, bus=1):
    sensor = bme680.BME680(address=addr, i2c_device=SMBus(bus))
    sensor.set_humidity_oversample(bme680.OS_2X)
    sensor.set_pressure_oversample(bme680.OS_4X)
    sensor.set_temperature_oversample(bme680.OS_8X)
    sensor.set_filter(bme680.FILTER_SIZE_3)
    sensor.set_gas_status(bme680.ENABLE_GAS_MEAS)
    sensor.set_gas_heater_temperature(320)
    sensor.set_gas_heater_duration(150)
    sensor.select_gas_heater_profile(0)

    print("Calentando sensor de gas 20 s…")
    time.sleep(20)
    for _ in range(10):
        if sensor.get_sensor_data():
            t = sensor.data.temperature
            rh = sensor.data.humidity
            p = sensor.data.pressure
            gas = sensor.data.gas_resistance if sensor.data.heat_stable else float('nan')
            print(f"T={t:.2f}C RH={rh:.1f}% P={p:.1f}hPa GAS={gas:.0f}Ω heat_stable={sensor.data.heat_stable}")
        time.sleep(1.0)

if __name__ == "__main__":
    main()

Compilación/flash/ejecución

No hay compilación; es Python. Sigue estrictamente estos pasos en la Raspberry Pi 3 Model B+ con Raspberry Pi OS Bookworm 64‑bit:

1) Preparar sistema

  • Actualiza índices APT y paquetes base:
  • sudo apt update
  • sudo apt full-upgrade -y
  • Instala utilidades e interfaces:
  • sudo apt install -y python3.11-venv python3-pip i2c-tools python3-gpiozero

2) Comprobar interfaces

  • I2C:
  • sudo i2cdetect -y 1
  • Debe aparecer 0x76 o 0x77.
  • UART:
  • ls -l /dev/serial0
  • groups $USER → debe incluir dialout e i2c (si no, cerrar sesión y volver a entrar o reboot)

3) Crear entorno de trabajo y venv

  • mkdir -p ~/rs485-modbus-greenhouse-control
  • cd ~/rs485-modbus-greenhouse-control
  • python3 -m venv .venv
  • source .venv/bin/activate
  • python -m pip install –upgrade pip==24.2
  • pip install pyserial==3.5 minimalmodbus==2.1.1 smbus2==0.5.0 bme680==1.1.1 gpiozero==1.6.2 typer==0.12.5 rich==13.9.3

4) Crear los scripts

  • Copia/pega el contenido de rs485_modbus_greenhouse.py en:
  • nano rs485_modbus_greenhouse.py
  • Hazlo ejecutable (opcional):
  • chmod +x rs485_modbus_greenhouse.py
  • Script de prueba BME680 (opcional):
  • nano bme680_check.py
  • chmod +x bme680_check.py

5) Ejecutar pruebas iniciales

  • BME680:
  • ./bme680_check.py
  • Debes ver lecturas plausibles de temperatura/humedad/presión y, tras estabilizar, gas_resistance distinto de NaN.
  • Modbus (sin lógica de control):
  • Si tienes un módulo de relés Modbus en ID=1, prueba a activar un relé manualmente usando minimalmodbus en REPL:
    • python
    • from serial.rs485 import RS485Settings
    • import serial, minimalmodbus
    • instr = minimalmodbus.Instrument(«/dev/serial0», 1)
    • instr.serial.baudrate=19200; instr.serial.parity=serial.PARITY_EVEN; instr.serial.timeout=0.3
    • instr.serial.rs485_mode=RS485Settings(rts_level_for_tx=True, rts_level_for_rx=False)
    • instr.mode=minimalmodbus.MODE_RTU
    • instr.write_bit(0, 1, functioncode=5) # enciende coil 0
    • instr.write_bit(0, 0, functioncode=5) # apaga coil 0
  • Si esto funciona, la conmutación DE/RE por RTS está operativa.

6) Ejecutar el controlador

  • Ejecución por defecto (IDs 1 y 2):
  • ./rs485_modbus_greenhouse.py –port /dev/serial0 –baud 19200 –parity E –timeout 0.2 –slave-relays 1 –slave-sensors 2 –bme-addr 0x76 –i2c-bus 1 –loop-period 2.0 –temp-target 26 –rh-target 65 –vpd-target 1.0
  • Ejecución sin módulo de sensores (solo relés):
  • ./rs485_modbus_greenhouse.py –slave-relays 1 –slave-sensors None
  • Observa el log: deben aparecer valores de T/RH/VPD y el estado de Fan/Pump/Heater/Mister en cada ciclo.

Validación paso a paso

1) Validación eléctrica y de bus:
– Verifica que el HAT RS485 tiene la terminación de 120 Ω activa solo si estás en un extremo del bus. En caso contrario, desactívala para evitar sobrecarga.
– Confirma polarización (bias) de la línea (pull‑up en A y pull‑down en B) en un único punto de la red. Muchos HAT lo ofrecen via jumpers.

2) Validación I2C (BME680):
– sudo i2cdetect -y 1 → aparecerá 0x76 o 0x77.
– Ejecuta ./bme680_check.py y verifica:
– Temperatura entre 15–40 °C (según ambiente).
– RH entre 20–90% (según ambiente).
– Presión 900–1100 hPa.
– gas_resistance > 5 kΩ tras unos 30–60 s (depende del aire ambiente).

3) Validación UART/RS485:
– Conecta el módulo de relés (ID=1) y energízalo.
– Desde el REPL de Python con minimalmodbus, escribe y lee una bobina (coil):
– instr.write_bit(0, 1, functioncode=5) → deberías oír clic o ver LED de relé encendido.
– instr.write_bit(0, 0, functioncode=5) → debe apagarse.
– Si inviertes A/B por error, no funcionará; corrige A↔B.

4) Validación del controlador completo:
– Ejecuta ./rs485_modbus_greenhouse.py con tus parámetros.
– Observa en el log, por ejemplo:
– T alta -> Fan=1, Heater=0.
– RH baja -> Mister=1.
– Soil<35% (si HR0 existe) -> Pump=1.
– Modifica el ambiente para comprobar respuestas:
– Calienta el sensor ligeramente con la mano → sube T; observa Fan.
– Exhala cerca del sensor → sube RH y gas; la lógica puede activar ventilación/nebulización.
– Verifica que los relés cambian consecuentemente (LEDs o salida).

5) Validación de estabilidad:
– Deja el sistema 10–15 minutos y confirma que la histéresis evita oscilaciones (no cambia de estado continuamente cerca de la consigna).

Troubleshooting

1) El puerto serie no responde (/dev/serial0 inexistente o en ttyS0 inestable):
– Asegúrate de haber añadido dtoverlay=pi3-disable-bt y enable_uart=1 en /boot/firmware/config.txt.
– Deshabilita el login por serial en raspi-config (Serial Port → Login shell: No).
– Revisa: ls -l /dev/serial0; debe apuntar a /dev/ttyAMA0 (PL011) en Pi 3 B+.

2) Conflicto de permisos al abrir el puerto:
– Añade tu usuario a dialout: sudo usermod -aG dialout $USER y reinicia sesión (o reboot).

3) A/B del RS485 invertidos:
– Síntoma: timeouts constantes, ninguna respuesta. Solución: invierte los conductores A y B en la bornera.

4) Falta de terminación/bias en el bus:
– Síntoma: lecturas/escrituras erráticas, CRCs incorrectos. Solución: habilita 120 Ω en los extremos, y una única pareja de resistencias de polarización en el bus.

5) RTS sin conmutar DE/RE:
– Verifica el jumper “DE/RE=RTS” en el HAT de Waveshare.
– Asegúrate de configurar instr.serial.rs485_mode con RS485Settings en el código.
– Comprueba dtoverlay=uart0,ctsrts=on para exponer la línea RTS en GPIO17.

6) BME680 no aparece en i2cdetect:
– Revisa cableado SDA/SCL/3V3/GND.
– Cambia la dirección a 0x77 si tu placa usa SDO a VCC, o a 0x76 si SDO a GND.
– Habilita I2C en raspi-config y reinicia.

7) Lectura de gas NaN o poco estable:
– El sensor necesita calentamiento (30–180 s). Asegura sensor.data.heat_stable antes de usar gas_resistance.
– Evita corrientes de aire directas o condensación.

8) Paridad/baudios incorrectos:
– Asegúrate de que todos los dispositivos Modbus usan la misma configuración (p.ej., 19200 8E1). Cambia –baud/–parity en el script según tus esclavos.

Mejoras/variantes

  • Integración con BSEC (librería de Bosch) para IAQ real y control avanzado basado en CO2 equivalente y VOC. Requiere SDK adicional y licencia; puede ejecutarse en el Pi, pero implica instalar binarios específicos.
  • Persistencia y visualización:
  • Exportar métricas a InfluxDB/Prometheus y visualizar en Grafana (temperatura, RH, VPD, estados de relés).
  • Registrar a SQLite/CSV con timestamps.
  • Supervisión remota:
  • Añadir una API REST (FastAPI) para leer estado y forzar modos manual/automático.
  • Implementar un servidor Modbus TCP que exponga el estado a un SCADA externo.
  • Robustez industrial:
  • Watchdog por hardware/software.
  • Retries con backoff exponencial y detección de esclavos caídos.
  • Separación de alimentación y EMC: uso de transceptores aislados y tierra de referencia dedicada.
  • Control más sofisticado:
  • Control PID para temperatura/humedad, con anti‑windup.
  • Calendarios y riegos por etapas según humedad de suelo y horario solar.

Checklist de verificación

  • [ ] Raspberry Pi 3 Model B+ con Raspberry Pi OS Bookworm 64‑bit instalada y actualizada.
  • [ ] dtoverlay=pi3-disable-bt, enable_uart=1 y dtparam=i2c_arm=on configurados en /boot/firmware/config.txt.
  • [ ] Grupos dialout e i2c asignados al usuario actual.
  • [ ] Waveshare RS485 HAT (SP3485) instalado; terminación/bias configurados; DE/RE en posición RTS.
  • [ ] BME680 cableado a 3V3, GND, SDA (GPIO2), SCL (GPIO3); detectado en i2cdetect (0x76/0x77).
  • [ ] Entorno virtual creado; pip 24.2; paquetes instalados con versiones fijadas (pyserial==3.5, minimalmodbus==2.1.1, smbus2==0.5.0, bme680==1.1.1, gpiozero==1.6.2).
  • [ ] Prueba de encendido de un relé Modbus (write_bit coil 0) funciona.
  • [ ] ./bme680_check.py muestra lecturas plausibles y gas estable tras calentamiento.
  • [ ] ./rs485_modbus_greenhouse.py corre sin errores; log muestra T/RH/VPD y estados de actuadores.
  • [ ] Validación funcional: cambios ambientales provocan respuestas esperadas (ventilación, nebulización, etc.).

Con este caso práctico, has configurado un sistema de control de invernadero “rs485-modbus-greenhouse-control” basado en Raspberry Pi 3 Model B+ con HAT RS485 (SP3485) y sensor BME680, utilizando Raspberry Pi OS Bookworm 64‑bit, Python 3.11 y una toolchain reproducible. Has cubierto desde la habilitación de interfaces y cableado, hasta el desarrollo, despliegue y validación del lazo de control sobre Modbus RTU en RS485.

Encuentra este producto y/o libros sobre este tema en Amazon

Ir a Amazon

Como afiliado de Amazon, gano con las compras que cumplan los requisitos. Si compras a través de este enlace, ayudas a mantener este proyecto.

Quiz rápido

Pregunta 1: ¿Qué sistema operativo se requiere para este proyecto?




Pregunta 2: ¿Cuál es la versión mínima de Python necesaria?




Pregunta 3: ¿Qué módulo de Python es necesario para crear entornos virtuales?




Pregunta 4: ¿Qué paquete APT es opcional si no se usa smbus2?




Pregunta 5: ¿Qué versión de pip se debe utilizar dentro del entorno virtual?




Pregunta 6: ¿Qué usuario debe pertenecer a los grupos necesarios para ejecutar el sistema?




Pregunta 7: ¿Qué línea de GPIO se utiliza como señal DE/RE para el control RS485?




Pregunta 8: ¿Qué sensor se utiliza por I2C en el bus 1?




Pregunta 9: ¿Qué tipo de acceso se requiere para trabajar en este proyecto?




Pregunta 10: ¿Qué versión del kernel de Linux se requiere para este proyecto?




Carlos Núñez Zorrilla
Carlos Núñez Zorrilla
Electronics & Computer Engineer

Ingeniero Superior en Electrónica de Telecomunicaciones e Ingeniero en Informática (titulaciones oficiales en España).

Sígueme:
Scroll al inicio