Monitorizando la salud de nuestras plantas con Home Assistant

En este capítulo aprenderemos a cómo controlar la salud de nuestras plantas de una manera sencilla y barata. Donde podremos conocer parámetros como la humedad que tiene, la temperatura, la cantidad de luz, o fertilizante entre otros.

 

Bien, creo que ya casi todos habéis escuchado alguna vez estos sensores, creo que los hay de muchas marcas y suelen ser el mismo cacharro al final, yo lo conozco como Xiaomi Mi Plant, aunque te lo podrás encontrar con cualquier nombre tipo Flower Care… en Banggood suelen poner algún descuento y por aproximadamente 10€ lo tienes.

El uso habitual sería con la app de turno del fabricante de turno que por bluetooth recoge los datos cada vez que se vea con tu móvil. En este caso al tener un Home Assistant que no tiene bluetooth, ya que en mi caso corre como una máquina virtual pues difícil tiene para hablarse con el sensor de marras. Así que al tener varias Raspberry Pi repartidas por casa, en cada Raspberry Pi que quede más cerca de la planta en cuestión, esa será la que conecte con el sensor, recoja los datos por bluetooth, y los almacene en una BD que tengo por ahí de MySQL/MariaDB; también es cierto que si te quieres saltar la BD podrías enviar los datos por MQTT, pero soy legacy, de andar almacenando las cosas en BD tradicional 🙂 Depende un poco la idea que tengas estoy seguro este post te va a ayudar.

sudo hcitool lescan

LE Scan ...
0A:4D:49:BA:19:XX (unknown)
0A:4D:49:BA:19:XX (unknown)
54:5F:A0:C4:A5:XX (unknown)
0A:4D:49:BA:19:XX (unknown)
C4:7C:8D:6B:25:XX (unknown)
C4:7C:8D:6B:25:XX Flower care
0A:4D:49:BA:19:XX (unknown)
54:5F:A0:C4:A5:XX (unknown)
...

 

Ahora vamos a instalar en esa Raspberry Pi la librería de Xiaomi Mi plant sensor para Python, y ya de paso el cliente de mysql que lo necesitaremos:

sudo pip3 install miflora
sudo pip3 install mysqlclient

 

Y ahora listo, sólo queda que os descarguéis este script, que no recuerdo bien de donde lo saqué para agradecérselo eternamente. Si os fijáis bien en la línea 47 le añadí unas líneas para que los datos que recoja los almacene en dicho servidor de MySQL, poned vuestros datos, así como el nombre de la planta.

#!/usr/bin/env python3
"""Demo file showing how to use the miflora library."""

import argparse
import re
import logging
import sys
import MySQLdb

from btlewrap import available_backends, BluepyBackend, GatttoolBackend, PygattBackend

from miflora.miflora_poller import MiFloraPoller, \
    MI_CONDUCTIVITY, MI_MOISTURE, MI_LIGHT, MI_TEMPERATURE, MI_BATTERY
from miflora import miflora_scanner

def valid_miflora_mac(mac, pat=re.compile(r"80:EA:CA:[0-9A-F]{2}:[0-9A-F]{2}:[0-9A-F]{2}")):
    """Check for valid mac adresses."""
    if not pat.match(mac.upper()):
        raise argparse.ArgumentTypeError('The MAC address "{}" seems to be in the wrong format'.format(mac))
    return mac

def poll(args):
    """Poll data from the sensor."""
    backend = _get_backend(args)
    poller = MiFloraPoller(args.mac, backend)
    #print("Getting data from Mi Flora")
    #print("FW: {}".format(poller.firmware_version()))
    print("Name: {}".format(poller.name()))
    print("Temperature: {}".format(poller.parameter_value(MI_TEMPERATURE)))
    print("Moisture: {}".format(poller.parameter_value(MI_MOISTURE)))
    print("Light: {}".format(poller.parameter_value(MI_LIGHT)))
    print("Conductivity: {}".format(poller.parameter_value(MI_CONDUCTIVITY)))
    print("Battery: {}".format(poller.parameter_value(MI_BATTERY)))
    nombre = (poller.name())
    temperatura = (poller.parameter_value(MI_TEMPERATURE))
    humedad = (poller.parameter_value(MI_MOISTURE))
    luz = (poller.parameter_value(MI_LIGHT))
    conductividad = (poller.parameter_value(MI_CONDUCTIVITY))
    bateria = (poller.parameter_value(MI_BATTERY))
    print (nombre)
    print (temperatura)
    print (humedad)
    print (luz)
    print (conductividad)
    print (bateria)
    nombre = "NOMBRE_DE_LA_PLANTA"
    db = MySQLdb.connect("DIRECCION_IP_SERVIDOR_MYSQL","USUARIO_BD","CONTRASEÑA_BD","NOMBRE_BD")
    cursor = db.cursor()
    cursor.execute("""INSERT INTO plantas (nombre,temperatura,humedad,luz,conductividad,bateria) VALUES (%s,%s,%s,%s,%s,%s) """,(nombre,temperatura,humedad,luz,conductividad,bateria))
    db.commit()

def scan(args):
    """Scan for sensors."""
    backend = _get_backend(args)
    print('Scanning for 10 seconds...')
    devices = miflora_scanner.scan(backend, 10)
    print('Found {} devices:'.format(len(devices)))
    for device in devices:
        print('  {}'.format(device))

def _get_backend(args):
    """Extract the backend class from the command line arguments."""
    if args.backend == 'gatttool':
        backend = GatttoolBackend
    elif args.backend == 'bluepy':
        backend = BluepyBackend
    elif args.backend == 'pygatt':
        backend = PygattBackend
    else:
        raise Exception('unknown backend: {}'.format(args.backend))
    return backend


def list_backends(_):
    """List all available backends."""
    backends = [b.__name__ for b in available_backends()]
    print('\n'.join(backends))


def history(args):
    """Read the history from the sensor."""
    backend = _get_backend(args)
    print('Getting history from sensor...')
    poller = MiFloraPoller(args.mac, backend)
    history_list = poller.fetch_history()
    print('History returned {} entries.'.format(len(history_list)))
    for entry in history_list:
        print('History from {}'.format(entry.wall_time))
        print("    Temperature: {}".format(entry.temperature))
        print("    Moisture: {}".format(entry.moisture))
        print("    Light: {}".format(entry.light))
        print("    Conductivity: {}".format(entry.conductivity))


def clear_history(args):
    """Clear the sensor history."""
    backend = _get_backend(args)
    print('Deleting sensor history data...')
    poller = MiFloraPoller(args.mac, backend)
    poller.clear_history()


def main():
    """Main function.

    Mostly parsing the command line arguments.
    """
    parser = argparse.ArgumentParser()
    parser.add_argument('--backend', choices=['gatttool', 'bluepy', 'pygatt'], default='gatttool')
    parser.add_argument('-v', '--verbose', action='store_const', const=True)
    subparsers = parser.add_subparsers(help='sub-command help', )

    parser_poll = subparsers.add_parser('poll', help='poll data from a sensor')
    parser_poll.add_argument('mac', type=valid_miflora_mac)
    parser_poll.set_defaults(func=poll)

    parser_scan = subparsers.add_parser('scan', help='scan for devices')
    parser_scan.set_defaults(func=scan)

    parser_scan = subparsers.add_parser('backends', help='list the available backends')
    parser_scan.set_defaults(func=list_backends)

    parser_history = subparsers.add_parser('history', help='get device history')
    parser_history.add_argument('mac', type=valid_miflora_mac)
    parser_history.set_defaults(func=history)

    parser_history = subparsers.add_parser('clear-history', help='clear device history')
    parser_history.add_argument('mac', type=valid_miflora_mac)
    parser_history.set_defaults(func=clear_history)

    args = parser.parse_args()

    if args.verbose:
        logging.basicConfig(level=logging.DEBUG)

    if not hasattr(args, "func"):
        parser.print_help()
        sys.exit(0)

    args.func(args)

if __name__ == '__main__':
    main()

 

 

Si queréis, os dejo el código que necesitaréis en MySQL para crear esta tabla, que al final es muy sencilla, tiene 7 campos, donde se almacena el nombre de la planta, la temperatura, la humedad, la luz en luxes, la conductividad, lo que le queda de batería y la fecha de cuando hizo el chequeo.

CREATE TABLE `plantas` (
    `nombre` CHAR(20) NULL COLLATE 'utf8mb4_general_ci',
    `temperatura` FLOAT NULL,
    `humedad` FLOAT NULL,
    `luz` FLOAT NULL,
    `conductividad` FLOAT NULL,
    `bateria` FLOAT NULL,
    `fecha` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
)
COLLATE='utf8mb4_general_ci'
ENGINE=InnoDB
ROW_FORMAT=COMPACT
;

 

Total, que lo que haremos es programar en la Raspberry Pi que ejecute este script con una tarea programada, por ejemplo con que recoja los datos cada 1 hora a mí me vale, añadimos en cron mediante ‘crontab -e’, y al final la dirección MAC bluetooth de vuestro sensor:

0 * * * * python3 /home/pi/miflora_jardin.py -v poll C4:7C:8D:6B:1E:XX
0 * * * * python3 /home/pi/miflora_portulaca.py -v poll 80:EA:CA:88:E4:XX

 

Ahora tenemos que recoger estos datos con Home Assistant, ¿cómo? Muy sencillo, ¡con consultas mysql! Así que en nuestro configuration.yaml podemos añadir de cada planta las siguientes consultas:

sensor:
...
  - platform: sql
    db_url: mysql://USUARIO_BD:CONTRASEÑA_BD@DIRECCION_IP_SERVIDOR_MYSQL/NOMBRE_DE_BD
    queries:
      - name: "Ciclamen - Temperatura"
        query: "SELECT temperatura FROM plantas WHERE nombre = 'Ciclamen' ORDER BY fecha DESC LIMIT 1;"
        column: 'temperatura'
        unit_of_measurement: 'ºC'

  - platform: sql
    db_url: mysql://USUARIO_BD:CONTRASEÑA_BD@DIRECCION_IP_SERVIDOR_MYSQL/NOMBRE_DE_BD
    queries:
      - name: "Ciclamen - Humedad"
        query: "SELECT humedad FROM plantas WHERE nombre = 'Ciclamen' ORDER BY fecha DESC LIMIT 1;"
        column: 'humedad'
        unit_of_measurement: '%'

  - platform: sql
    db_url: mysql://USUARIO_BD:CONTRASEÑA_BD@DIRECCION_IP_SERVIDOR_MYSQL/NOMBRE_DE_BD
    queries:
      - name: "Ciclamen - Luz"
        query: "SELECT luz FROM plantas WHERE nombre = 'Ciclamen' ORDER BY fecha DESC LIMIT 1;"
        column: 'luz'
        unit_of_measurement: 'lux'

  - platform: sql
    db_url: mysql://USUARIO_BD:CONTRASEÑA_BD@DIRECCION_IP_SERVIDOR_MYSQL/NOMBRE_DE_BD
    queries:
      - name: "Ciclamen - Conductividad"
        query: "SELECT conductividad FROM plantas WHERE nombre = 'Ciclamen' ORDER BY fecha DESC LIMIT 1;"
        column: 'conductividad'
        unit_of_measurement: 'Ω'

  - platform: sql
    db_url: mysql://USUARIO_BD:CONTRASEÑA_BD@DIRECCION_IP_SERVIDOR_MYSQL/NOMBRE_DE_BD
    queries:
      - name: "Ciclamen - Bateria"
        query: "SELECT bateria FROM plantas WHERE nombre = 'Ciclamen' ORDER BY fecha DESC LIMIT 1;"
        column: 'bateria'
        unit_of_measurement: '%'
...

 

Y ahora sí llegó el momento de añadir el componente de Plant para integrar estos valores que estamos recogiendo, e indicando a qué le corresponden, así que añadiríamos algo como lo siguiente en configuration.yaml:

plant:
  # Cyclamen
  ciclamen:
    sensors:
      moisture: sensor.ciclamen_humedad
      battery: sensor.ciclamen_bateria
      temperature: sensor.ciclamen_temperatura
      conductivity: sensor.ciclamen_conductividad
      brightness: sensor.ciclamen_luz
    min_moisture: 15
    max_moisture: 75
    min_battery: 10
    min_conductivity: 250
    max_conductivity: 2000
    min_temperature: 1
    max_temperature: 35
    min_brightness: 2000
    max_brightness: 40000
    check_days: 3

 

Bien, ¿y qué valores corresponden con los de nuestra planta? Aquí os dejo una hoja de Google Spreadsheet donde podéis obtener info de alguna planta, si no aproximadamente podéis indicarlos buscando un poco en San Google.

 

Como siempre, tras tocar fichero de configuración, reiniciamos nuestro Home Assistant y desde la UI de Lovelace podremos añadir ya nuestras tarjetas con nuestras plantas o flores, o jardín o árbol o lo que queramos controlar. Escogemos una tarjeta de ‘Estado de la planta’ e indicamos la entidad de la planta y ¡zas! trabajo realizado. Ya podremos conocer el estado en todo momento de nuestras plantas.

 

Y ahora nos queda la guinda del pastel, vaya que cuando pase algo que nos alerte, por ejemplo, si la humedad es baja, pues eso es que la planta se está secando, que mejor que mandar un Telegram o una alerta por nuestros altavoces de casa para indicárnoslo.

Como siempre, agradeceros si habéis llegado hasta el final y esperando haber podido ayudar, y gracias sobre todo por compartir en redes sociales y esos likes!

 

Héctor Herrero