Xiaomi Mi Plants Monitor Flower

Just to add, if I # out temperature it works… and I don’t really use temperature so it’s ok from my side, but just in case anyone else comes across this, would be good to know where I was going wrong…

I just received a new order of plant sensors. Unfortunately, I realized that it is the chinese version. This version still works with HASS, but can not be added to the MI Home app, nor to the flower care app. It keeps saying, that this product is only for user at Mainland China. Even when the location of the iPhone is set to Hong Kong and the location of the MI Home app to Mainland China.

Has anyone managed to connect it to an iOS app? I just want to connect them to the app for updating the firmware to the latest version.

I have the exact issue with the plant component. min_temperature does not work. Not sure where to report this as a bug

I had the same issue but this was fixed after the upgrade to 0.44.1 (as explained in the release notes)

Release 0.44.1 - May 7

  • Fix opening add-ons in Hass.io store that are not installed (@balloob)
  • sensor.envirophat: add missing requirement (@imrehg - #7451) (sensor.envirophat docs)
  • Forecasts for weather underground (@pezinek - #7062) (sensor.wunderground docs)
  • Upgrade pymysensors to 0.10.0 (@MartinHjelmare - #7469)
  • Fix plant MIN_TEMPERATURE, MAX_TEMPERATURE validation (@frog32 - #7476) (plant docs)
  • Update to pyunifi 2.12 (@finish06 - #7468) (device_tracker.unifi docs)
  • MQTT Cover: Fixed status reporting for range with non-zero base (@cribbstechnologies)

Does anyone know how to find the right moisture percentage and conductivity for a specific plant? Was hoping to find it in the miflore app, but based on that I can only guesstimate the ideal values…

I have been trying to decipher the android app, but i haven’t found out how to make the query in English, and i don’t understand Chinese…

But this is what i have so far:
HTTP
Request:

POST /api/ HTTP/1.1
Host: api.huahuacaocao.net
Content-Type: application/json

{
  "service": "pkbes",
  "method": "GET",
  "path": "/v2/pkb/categoryspecies",
  "data": {
    "extra": {
      "version": "ASI_3014_3.0.0",
      "position": [0,0],
      "app_channel": "release",
      "country": "sweden",
      "lang": "en",
      "phone": "man_model_sdk_int"
    },
    "phyllotaxy": "",
    "shape": "",
    "apex": "",
    "margin": "",
    "category": "",
    "count": 2,
    "limit": 2,
    "model": ""
  }
}

Response:

{
  "status": 100,
  "data": {
    "url": "http://pkb.resource.huahuacaocao.net/",
    "species": [
      {
        "name": "阿月浑子",
        "count": 1,
        "display_name": "阿月浑子",
        "img_url": "http://pkbes.resource.huahuacaocao.net/6Zi/5pyI5rWR5a2QLmpwZw==?imageView2/1/w/%d/h/%d"
      },
      {
        "name": "矮丛小金雀",
        "count": 1,
        "display_name": "矮丛小金雀",
        "img_url": "http://pkbes.resource.huahuacaocao.net/55+u5Lib5bCP6YeR6ZuALmpwZw==?imageView2/1/w/%d/h/%d"
      }
    ],
    "sum": 2201
  }
}

You can use that query to search some how, i haven’t figured out yet what to put in the different fields.

Just for information, I have tried a couple of different USB Bluetooth LE devices in my server, and I can reach the closest plant but terrible range otherwise.
Strange thing is, when I try to fetch data using the app and my Samsung S7, it has great range and I can basically be in any room in my apartment.

So I setup a Raspberry Pi 3 with the built-in bluetooth and the RPI3 has the same great range as my phone and it is working excellent! :smiley:
(using the plantgateway, which collects the data and publish the data via MQTT)

yeah, the plantgateway is working for me as well! However I tried setting it up with PiBakery, and everything works except for the cron entry to run it every 30min / 1 hour. What entry do you use?

I am running it in a docker-container on raspbian.
Nevertheless, its a cronjob inside the docker container which looks like this:

PATH=/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin
LD_LIBRARY_PATH=/usr/local/lib
*/15 * * * * /usr/local/bin/plantgateway >> /var/log/plantgw.log 2>&1
@reboot /usr/local/bin/plantgateway >> /var/log/plantgw.log 2>&1

The first two is set becasue cron runs in a minimal shell environment, so it needs the paths.
But this is after my troubleshooting, so I am not sure if they are required.
Perhaps it is enough to just set the full path to plantgateway as I have done in the last two rows. Anyway, it is working :slight_smile:
I am running it every 15 min and at reboot right now, but will probably adjust it to 30/min or 60 min interval as you suggested.

1 Like

Hi,

Do you had solve the problem with de location in the iPhone? If so how do you solved?
Thanks in advance. :slight_smile:

No, I was not able to use the app on the iPhone. But the chinese version works like the international version via HASS and an bluetooth BLE stick.

Hi, trying to write here. Im very interested in this modified poller. Have you got a fork on github?

I solved de location problem on iOS by not allowing the Flower Care app access to GPS and using FlyVPN when adding a new sensor. After that you don’t need FlyVPN any more and they work fine.

Hi,

I didn’t do a proper git pull for this change, I just modified my local files to make it work. I’m definitely not an experienced coder, and there is some issue with the program throwing errors (I think it happens when the computer cannot reach the device), but aside from the occasional error, it does successfully poll multiple MiFloras from my Mac (I’ve got 5 connected currently). Here’s my code:

""""
Read data from Mi Flora plant sensor.

Reading from the sensor is handled by the command line tool "gatttool" that
is part of bluez on Linux.
No other operating systems are supported at the moment
"""

import sys
from datetime import datetime, timedelta
from threading import Lock, current_thread
import logging
import pygatt

MI_TEMPERATURE = "temperature"
MI_LIGHT = "light"
MI_MOISTURE = "moisture"
MI_CONDUCTIVITY = "conductivity"
MI_BATTERY = "battery"


def write_ble(mac, handle, value, retries=10, timeout=20):
    """
    Read from a BLE address

    @param: mac - MAC address in format XX:XX:XX:XX:XX:XX
    @param: handle - BLE characteristics handle in format 0xXX
    @param: value - value to write to the given handle
    @param: timeout - timeout in seconds
    """

    attempt = 0

    while attempt <= retries:
    
        while True:
    
            try:
                adapter = pygatt.BGAPIBackend()
                adapter.start()
                device = adapter.connect(mac)
                device.char_write_handle(handle, value)
                adapter.stop()
            
                return True
        
            except: 
                attempt += 1
                pass

    return False


def read_ble(mac, handle, retries=10, timeout=20):
    """
    Read from a BLE address

    @param: mac - MAC address in format XX:XX:XX:XX:XX:XX
    @param: handle - BLE characteristics handle in format 0xXX
    @param: timeout - timeout in seconds
    """

    attempt = 0
    
    while attempt <= retries:
    
        while True:
    
            try:
                adapter = pygatt.BGAPIBackend()
                adapter.start()
                device = adapter.connect(mac)
                data = device.char_read_handle(handle)
                adapter.stop()
            	
                return data
        
            except: 
                attempt += 1
                pass

    return None
    
def write_read_ble(mac, retries=10, timeout=20):
    """
    Write and then read sensor data from BLE address

    @param: mac - MAC address in format XX:XX:XX:XX:XX:XX
    @param: handle - BLE characteristics handle in format 0xXX
    @param: timeout - timeout in seconds
    """

    attempt = 0
    
    while attempt <= retries:
    
        while True:
    
            try:
                adapter = pygatt.BGAPIBackend()
                adapter.start()
                device = adapter.connect(mac)
                device.char_write_handle(0x0033, bytearray([0xa0, 0x1f]))
                data=device.char_read_handle(0x0035)
                adapter.stop()
            	
                return data
        
            except: 
                attempt += 1
                pass

    return None


class MiFloraPoller(object):
    """"
    A class to read data from Mi Flora plant sensors.
    """

    def __init__(self, mac, adapter, cache_timeout=600, retries=3):
        """
        Initialize a Mi Flora Poller for the given MAC address.
        """

        self._mac = mac
        self.adapter = adapter
        self._cache = None
        self._cache_timeout = timedelta(seconds=cache_timeout)
        self._last_read = None
        self._fw_last_read = datetime.now()
        self.retries = retries
        self.ble_timeout = 10
        self.lock = Lock()
        self._firmware_version = None

    def name(self):
        """
        Return the name of the sensor.
        """
        name = read_ble(self._mac, 0x0003,
                        retries=self.retries,
                        timeout=self.ble_timeout)
        return ''.join(chr(n) for n in name)

    def fill_cache(self):
        firmware_version = self.firmware_version()
        if not firmware_version:
            # If a sensor doesn't work, wait 5 minutes before retrying
            self._last_read = datetime.now() - self._cache_timeout + \
                timedelta(seconds=300)
            return

        self._cache = write_read_ble(self._mac,
                               retries=self.retries,
                               timeout=self.ble_timeout)
        self._check_data()
        
        if self._cache is not None:
            self._last_read = datetime.now()
            
        else:
            # If a sensor doesn't work, wait 5 minutes before retrying
            self._last_read = datetime.now() - self._cache_timeout + \
                timedelta(seconds=300)

    def battery_level(self):
        """
        Return the battery level.

        The battery level is updated when reading the firmware version. This
        is done only once every 24h
        """
        self.firmware_version()
        return self.battery

    def firmware_version(self):
        """ Return the firmware version. """
        if (self._firmware_version is None) or \
                (datetime.now() - timedelta(hours=24) > self._fw_last_read):
            self._fw_last_read = datetime.now()
            res = read_ble(self._mac, 0x0038, retries=self.retries)
            if res is None:
                self.battery = 0
                self._firmware_version = None
            else:
                self.battery = res[0]
                self._firmware_version = "".join(map(chr, res[2:]))
        return self._firmware_version

    def parameter_value(self, parameter, read_cached=True):
        """
        Return a value of one of the monitored paramaters.

        This method will try to retrieve the data from cache and only
        request it by bluetooth if no cached value is stored or the cache is
        expired.
        This behaviour can be overwritten by the "read_cached" parameter.
        """

        # Special handling for battery attribute
        if parameter == MI_BATTERY:
            return self.battery_level()

        # Use the lock to make sure the cache isn't updated multiple times
        with self.lock:
            if (read_cached is False) or \
                    (self._last_read is None) or \
                    (datetime.now() - self._cache_timeout > self._last_read):
                self.fill_cache()
#             else:
#                 LOGGER.debug("Using cache (%s < %s)",
#                              datetime.now() - self._last_read,
#                              self._cache_timeout)

        if self._cache and (len(self._cache) == 16):
            return self._parse_data()[parameter]
        else:
            self.fill_cache()
            raise IOError("Could not read data from Mi Flora sensor %s",
                          self._mac)

    def _check_data(self):
        if self._cache is None:
            return
        if self._cache[7] > 100: # moisture over 100 procent
            self._cache = None
            return
        if self._firmware_version >= "2.6.6":
            if sum(self._cache[10:]) == 0:
                self._cache = None
                return
        if sum(self._cache) == 0:
            self._cache = None
            return None

    def _parse_data(self):
        data = self._cache
        res = {}
        res[MI_TEMPERATURE] = float(data[1] * 256 + data[0]) / 10
        res[MI_MOISTURE] = data[7]
        res[MI_LIGHT] = data[4] * 256 + data[3]
        res[MI_CONDUCTIVITY] = data[9] * 256 + data[8]
        return res

I’d be happy to hear any suggestions on cleaning up the code. As I said I’m not a very experienced coder, so I’m sure it’s pretty rough.

Very Nice of you! Can I make å github fork and reference you for the future development of this code?

Sure thing!

this worked like a charm… tks

  1. Do you have to connect the Miflora monitor to a phone app first?
  2. Does this work with Pi Zero W with Hass.io?

I set up bluetooth with a Pi Zero W fresh install of Hass.io and added a configuration.yaml entry just like the example here https://home-assistant.io/components/sensor.miflora/ (including the mac address as found with a Pi3). No other components added (so nothing else using BT). The sensors show up, but no data (except battery 0%, but I replaced the battery, too). It’s the Chinese sensor.

Im having the same issue, im think im going to change to Hassbian instead seems like you have more options to fiddle around in the os there…

I love the idea of hassio (docker and all) but its to locked down…

Edit: I’m using raspberry pi 3 not Zero W, so i guess you might not have the exact same problem that i have…

1 Like

I could be wrong, but I think BLE isn’t working on Pi Zero W with Hassio, yet. The documentation says that the BT add-on is for Pi 3. When I add BT device tracker to config it works perfect on Pi Zero. When I switch to BLE device tracker the Pi Zero won’t even load Hassio on reboot. Adding logger entries for Miflora to configuration.yaml shows that it’s just never connecting. I hooked it up to a phone and even small moisture adjustments sync super fast, so the device is working great but BLE on Pi Zero W just isn’t connecting with it.

But, yeah, SSH is locked down too much for most current users and I’m guessing someone will write a fully functional SSH plugin in a month or two for Hassio and full BLE support for Pi Zero W pretty soon, too. It’s crazy how many highly skilled people are donating so many hours to this great project :wink: It’s the best product out there and it’s free!!! [FWIW forum won’t let me put a dot in Hassio]

1 Like