Xiaomi Mi Plants Monitor Flower

guide
Tags: #<Tag:0x00007f20586234a8>

#203

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


#204

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.


#205

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.


#206

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


#207

Sure thing!


#208

this worked like a charm… tks


#210
  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.


#211

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…


#212

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]


#213

Someone already got this working with macOS? Got an error that the polling got no data.


#214

Thank you all for the great work you did in making this sensor work for Home assistant!

I came along a few weeks ago and installed the Miflora sensor, scanned and found the MAC addresses of the sensors and installed them all in HA. I even activated an automation that waters plants in my greenhouse based on the moisture sensor data.

I few day ago I also activated Homebridge to get HA values into my iOS and Siri routines. I even got data from these sensors into the Home app and Siri reports them. But it’s only temperature and lux values, not moisture nor conductivity.

Does anybody have an idea to why?


#215

Maybe the home app doesn’t support those measurements? A workaround may be to put them in a template sensor.


#216

Good evening! I just did a fresh installation of hassbian on my Pi3 and ran into some issues with pairing with my Flower sensor. In my previous installation things worked fine and I got data from the sensor. The difference with my fresh installation is that i installed Flic smart bluetooth buttons (with this installation process: Install Flic). The buttons are working perfectly, so the Bluetooth is obviously working. However, then when I try to find and pair with the sensor I can’t find anything.

When I run “hcitool scan” in the terminal i get this:

Scanning …
Inquiry failed: Device or resource busy

I then tried:

bluetoothctl
[bluetooth]# agent on
Agent registered
[bluetooth]# default-agent
Default agent request successful
[bluetooth]# scan on
No default controller available

I saved the mac-address from my precious installation, but it does not help to just add it with the regular config-lines

sensor: 
    - platform: miflora
      mac: 'XX:XX:XX:XX:XX:XX'
      name: Mr Avocado
      force_update: false
      median: 1
      monitored_conditions:
        - moisture
        - light
        - temperature
        - conductivity
        - battery

Any ideas?


#217

Is the flower sensor paired with your phone while you try to access it from hass? If so, it won’t work to communicate with it.


#218

Thanks for the response!

Of course, this is why. But how to add that functionality?
There is a list of supported types from HA that goes into Homebridge settings. The standard HA settings include: climate and sensor (which one would imagine would suffice).

I tried adding the value type of moisture into the Homebridge settings and restarted, but nothing changes. Automatically Homebridge includes temperature and light intensity from the Mi Flora.


#219

I found a working wrapper for BLE in macOS, can scan my devices and found the Mi Flora. One problem, can program python, so don’t know how to begin and use it for the poller of the mi flora.


#220

Well, it is paired with my phone. So I shut down the bluetooth on my phone, but I still have the “No default controller available” when I try to pair it (or use scan on) in bluetoothctl.


#221

Anyone got this working on a Pi Zero W?


#222

I’m using plantgateway on a Pi Zero W to publish the value of six Xiaomi Mi sensors on a MQTT topic without issue and I have configured the Plant Monitor component to monitor the plants.

The only issue I’m facing is the display of the status of the plant. I’m only manage to get “ok” or “problem” and have to click to see what the problem is (e.g. conductivity low).


#223

I have setup 3 of them, only 1 get its details.

sensor:

  • platform: miflora
    mac: C4:7C:8D:60:95:5F
    name: Vinca Minor
    Median: 3
    adapter: hci0
    monitored_conditions:
    • temperature
    • light
    • conductivity
    • battery
    • moisture
  • platform: miflora
    mac: C4:7C:8D:65:BF:70
    name: Palm achtertuin rechts
    Median: 3
    adapter: hci0
    monitored_conditions:
    • temperature
    • light
    • conductivity
    • battery
    • moisture
  • platform: miflora
    mac: C4:7C:8D:65:BE:A5
    name: Palm achtertuin links
    Median: 3
    adapter: hci0
    monitored_conditions:
    • temperature
    • light
    • conductivity
    • battery
    • moisture

am im doing this right in yaml??