Xiaomi MiFlora is a cost effective plant sensor, which uses Bluetooth Low Energy (BLE) to communicate wirelessly, and is capable of sensing temperature, soil humidity, light, soil fertility and it’s own battery level. You can find detailed info about this smart sensor on the official Xiaomi site here. Daniel Matuschek (openha) has written a library for the device, and using his python code which is capable of reading these data, and I have been running it on a Raspberry Pi 3.

The script can be run in the command line, and the output looks like this:

pi@pi3pimatic:~ $ sudo python3 /home/pi/miflora/demo.py
Getting data from Mi Flora
FW: 2.6.2
Name: Flower mate
Temperature: 28.9
Moisture: 2
Light: 112
Conductivity: 0
Battery: 100

As I’m using pimatic for my home automation projects, I wanted to come up with a way to display the MiFlora’s data on my pimatic web interface dashboard. I’m not skilled enough to create a full blown pimatic plugin, but with a simple workaround of using 3 already existing pimatic plugins (pimatic-cron, pimatic-shell-execute, pimatic-log-reader) the data can be displayed in pimatic. In the tutorial I will assume that you have pimatic up and running on your Pi3, there’s an excellent guide to get you started.

So let’s go though every step you need to take to get your MiFlora send data to Pimatic.

Necessary hardware

  • Raspberry Pi 3 with Raspbian (Lite) installed, and having a static IP
  • Xiaomi MiFlora plant sensor

(Other Raspberry Pi types like the Pi2/Pi B+/Zero will not work, as the Pi3 is the only Pi with Bluetooth to date).

Checking if the sensor works, using the Android app

Open the panel of the MiFlora and pull out the plastic peg so the battery gets in contact and the sensor powers up (the LED at the top blinks a few times).

MiFlora panel (image courtesy of Xiaomi)

MiFlora is using a CR2032 battery which is located under the protective panel (image: xiaomi-mi.com)

Install Flower care app on your Android (BLE enabled) phone. You can find the app on the Play Store.

Turn on Bluetooth and Location services (GPS!) on your phone. It’s strange, but enabling Bluetooth on my phone wasn’t enough, my phone couldn’t find the MiFlora with the Flower care app, only if location access (GPS) was enabled as well.

Start the Flower care app on your phone, and add the sensor using the + button. Hold the MiFlora close to the phone while it’s scanning. As soon as if finds the MiFlora, bind it. It offers to add a plant, do so. After this the MiFlora starts sending the plant’s data to your phone. Now, we know that the MiFlora sensor works. Now exit the app and disable Bluetooth so it won’t interfere with the Raspberry Pi 3 Bluetooth.

Note: When I first started my MiFlora, it was running firmware version 2.6.2, but since then the Flower care app offered to update it to firmware 2.7, and then to firmware 2.8.6. This works with the latest MiFlora library.

As the next step, log into your Pi3 via SSH or locally. Pi3 has a Bluetooth BLE chip on board, it just needs to be enabled.

Install the “bluez” package, if it’s not already done (mine was already installed):

pi@pi3pimatic:~ $ sudo apt-get install bluez
Reading package lists... Done
Building dependency tree
Reading state information... Done
bluez is already the newest version.
bluez set to manually installed.
0 upgraded, 0 newly installed, 0 to remove and 29 not upgraded.

Then test if we can see the BLE device MiFlora (named Flower mate) – keep the MiFlora close to the Pi3 now.

pi@pi3pimatic:~ $ sudo hcitool lescan
LE Scan ...
C4:7C:8D:61:57:A0 (unknown)
C4:7C:8D:61:57:A0 Flower mate

(you can only exit by pressing Ctrl+C)

Now we know the MAC address of the MiFlora, let’s test if we get any data:

pi@pi3pimatic:~ $ gatttool --device=C4:7C:8D:61:57:A0 --char-read -a 0x35
Characteristic value/descriptor: 14 01 00 15 00 00 00 02 00 00 00 00 00 00 00 00

Great, we get data! (If we got only zeros, that would mean that there’s some issue in the communication or the sensor is faulty)

To run the script we need to have python3 installed, which is not installed on Raspbian Lite by default, so we need to get it:

pi@pi3pimatic:~ $ sudo apt-get install python3
Reading package lists... Done Building dependency tree Reading state information... Done The following extra packages will be installed: dh-python libmpdec2 libpython3-stdlib libpython3.4-minimal libpython3.4-stdlib python3-minimal python3.4 python3.4-minimal Suggested packages: python3-doc python3-tk python3-venv python3.4-venv python3.4-doc binfmt-support The following NEW packages will be installed: dh-python libmpdec2 libpython3-stdlib libpython3.4-minimal libpython3.4-stdlib python3 python3-minimal python3.4 python3.4-minimal 0 upgraded, 9 newly installed, 0 to remove and 29 not upgraded. Need to get 4,259 kB of archives. After this operation, 17.2 MB of additional disk space will be used. Do you want to continue? [Y/n] y Get:1 http://mirrordirector.raspbian.org/raspbian/ jessie/main libmpdec2 armhf 2.4.1-1 [65.8 kB] Get:2 http://mirrordirector.raspbian.org/raspbian/ jessie/main libpython3.4-minimal armhf 3.4.2-1 [483 kB] Get:3 http://mirrordirector.raspbian.org/raspbian/ jessie/main libpython3.4-stdlib armhf 3.4.2-1 [2,011 kB] Get:4 http://mirrordirector.raspbian.org/raspbian/ jessie/main python3.4-minimal armhf 3.4.2-1 [1,355 kB] ...and so on

And now we get the script! Hereby I’d like to thank Daniel Matuschek (openha) for this amazing little piece of code.

pi@pi3pimatic:~ $ git clone https://github.com/open-homeautomation/miflora.git
Cloning into 'miflora'... remote: Counting objects: 204, done. remote: Total 204 (delta 0), reused 0 (delta 0), pack-reused 204 Receiving objects: 100% (204/204), 70.03 KiB | 0 bytes/s, done. Resolving deltas: 100% (95/95), done. Checking connectivity... done.
pi@pi3pimatic:~ $ cd miflora/
pi@pi3pimatic:~/miflora $ ls
build.sh demo.py dist LICENSE miflora README.rst setup.py test.sh

Then we install some dependencies, like setuptools:

pi@pi3pimatic:~ $ wget https://bootstrap.pypa.io/ez_setup.py -O - | sudo python
--2016-12-02 10:30:47-- https://bootstrap.pypa.io/ez_setup.py
Resolving bootstrap.pypa.io (bootstrap.pypa.io)... 151.101.12.175
Connecting to bootstrap.pypa.io (bootstrap.pypa.io)|151.101.12.175|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 12746 (12K) [text/x-python]
Saving to: ‘STDOUT’

- 100%[======================================================================================================>] 12.45K --.-KB/s in 0.002s

2016-12-02 10:30:47 (6.15 MB/s) - written to stdout [12746/12746]

Downloading https://pypi.io/packages/source/s/setuptools/setuptools-30.0.0.zip
Extracting in /tmp/tmpggHguy
Now working in /tmp/tmpggHguy/setuptools-30.0.0
Installing Setuptools
running install
running bdist_egg
running egg_info
writing requirements to setuptools.egg-info/requires.txt
writing setuptools.egg-info/PKG-INFO
writing top-level names to setuptools.egg-info/top_level.txt
writing dependency_links to setuptools.egg-info/dependency_links.txt
writing entry points to setuptools.egg-info/entry_points.txt
reading manifest file 'setuptools.egg-info/SOURCES.txt'
reading manifest template 'MANIFEST.in'
warning: no files found matching '*' under directory 'setuptools/_vendor'
writing manifest file 'setuptools.egg-info/SOURCES.txt'
installing library code to build/bdist.linux-armv7l/egg
running install_lib
running build_py
creating build

...
long output skipped
...

creating dist
creating 'dist/setuptools-30.0.0-py2.7.egg' and adding 'build/bdist.linux-armv7l/egg' to it
removing 'build/bdist.linux-armv7l/egg' (and everything under it)
Processing setuptools-30.0.0-py2.7.egg
Copying setuptools-30.0.0-py2.7.egg to /usr/local/lib/python2.7/dist-packages
Adding setuptools 30.0.0 to easy-install.pth file
Installing easy_install script to /usr/local/bin
Installing easy_install-2.7 script to /usr/local/bin

Installed /usr/local/lib/python2.7/dist-packages/setuptools-30.0.0-py2.7.egg
Processing dependencies for setuptools==30.0.0
Finished processing dependencies for setuptools==30.0.0

Then we run the setup.py

pi@pi3pimatic:~ $ cd miflora
pi@pi3pimatic:~/miflora $ sudo python setup.py install
running install
running bdist_egg
running egg_info
creating miflora.egg-info
writing miflora.egg-info/PKG-INFO
writing top-level names to miflora.egg-info/top_level.txt
writing dependency_links to miflora.egg-info/dependency_links.txt
writing manifest file 'miflora.egg-info/SOURCES.txt'
reading manifest file 'miflora.egg-info/SOURCES.txt'
writing manifest file 'miflora.egg-info/SOURCES.txt'
installing library code to build/bdist.linux-armv7l/egg
running install_lib
running build_py
creating build
creating build/lib.linux-armv7l-2.7
creating build/lib.linux-armv7l-2.7/miflora
copying miflora/miflora_poller.py -> build/lib.linux-armv7l-2.7/miflora
copying miflora/__init__.py -> build/lib.linux-armv7l-2.7/miflora
creating build/bdist.linux-armv7l
creating build/bdist.linux-armv7l/egg
creating build/bdist.linux-armv7l/egg/miflora
copying build/lib.linux-armv7l-2.7/miflora/miflora_poller.py -> build/bdist.linux-armv7l/egg/miflora
copying build/lib.linux-armv7l-2.7/miflora/__init__.py -> build/bdist.linux-armv7l/egg/miflora
byte-compiling build/bdist.linux-armv7l/egg/miflora/miflora_poller.py to miflora_poller.pyc
byte-compiling build/bdist.linux-armv7l/egg/miflora/__init__.py to __init__.pyc
creating build/bdist.linux-armv7l/egg/EGG-INFO
copying miflora.egg-info/PKG-INFO -> build/bdist.linux-armv7l/egg/EGG-INFO
copying miflora.egg-info/SOURCES.txt -> build/bdist.linux-armv7l/egg/EGG-INFO
copying miflora.egg-info/dependency_links.txt -> build/bdist.linux-armv7l/egg/EGG-INFO
copying miflora.egg-info/not-zip-safe -> build/bdist.linux-armv7l/egg/EGG-INFO
copying miflora.egg-info/top_level.txt -> build/bdist.linux-armv7l/egg/EGG-INFO
creating 'dist/miflora-0.1.13-py2.7.egg' and adding 'build/bdist.linux-armv7l/egg' to it
removing 'build/bdist.linux-armv7l/egg' (and everything under it)
Processing miflora-0.1.13-py2.7.egg
creating /usr/local/lib/python2.7/dist-packages/miflora-0.1.13-py2.7.egg
Extracting miflora-0.1.13-py2.7.egg to /usr/local/lib/python2.7/dist-packages
Adding miflora 0.1.13 to easy-install.pth file

Installed /usr/local/lib/python2.7/dist-packages/miflora-0.1.13-py2.7.egg
Processing dependencies for miflora==0.1.13
Finished processing dependencies for miflora==0.1.13

Some other dependencies:
sudo apt-get install pkg-config libboost-python-dev libboost-thread-dev libbluetooth-dev >= 4.101 libglib2.0-dev python-dev

After all that installs (I didn’t include the long output), we need to install python-pip:

pi@pi3pimatic:~/miflora $ sudo apt-get install python-pip
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following extra packages will be installed:
python-chardet python-colorama python-distlib python-html5lib python-ndg-httpsclient python-openssl python-pyasn1 python-requests python-setuptools python-six
python-urllib3 python-wheel
Suggested packages:
python-genshi python-lxml python-openssl-doc python-openssl-dbg doc-base
Recommended packages:
python-dev-all
The following NEW packages will be installed:
python-chardet python-colorama python-distlib python-html5lib python-ndg-httpsclient python-openssl python-pip python-pyasn1 python-requests python-setuptools
python-six python-urllib3 python-wheel
0 upgraded, 13 newly installed, 0 to remove and 29 not upgraded.
Need to get 1,144 kB of archives.
After this operation, 4,854 kB of additional disk space will be used.
Do you want to continue? [Y/n] y
Get:1 http://mirrordirector.raspbian.org/raspbian/ jessie/main python-chardet all 2.3.0-1 [96.2 kB]
Get:2 http://mirrordirector.raspbian.org/raspbian/ jessie/main python-colorama all 0.3.2-1 [20.3 kB]
Get:3 http://mirrordirector.raspbian.org/raspbian/ jessie/main python-distlib all 0.1.9-1 [113 kB]
Get:4 http://mirrordirector.raspbian.org/raspbian/ jessie/main python-six all 1.8.0-1 [12.6 kB]
Get:5 http://mirrordirector.raspbian.org/raspbian/ jessie/main python-html5lib all 0.999-3 [84.0 kB]
Get:6 http://mirrordirector.raspbian.org/raspbian/ jessie/main python-openssl armhf 0.13.1-2 [85.5 kB]
Get:7 http://mirrordirector.raspbian.org/raspbian/ jessie/main python-ndg-httpsclient all 0.3.2-1 [20.5 kB]
Get:8 http://mirrordirector.raspbian.org/raspbian/ jessie/main python-urllib3 all 1.9.1-3 [55.4 kB]
Get:9 http://mirrordirector.raspbian.org/raspbian/ jessie/main python-requests all 2.4.3-6 [204 kB]
Get:10 http://mirrordirector.raspbian.org/raspbian/ jessie/main python-setuptools all 5.5.1-1 [242 kB]
Get:11 http://mirrordirector.raspbian.org/raspbian/ jessie/main python-pip all 1.5.6-5 [114 kB]
Get:12 http://mirrordirector.raspbian.org/raspbian/ jessie/main python-pyasn1 all 0.1.7-1 [49.3 kB]
Get:13 http://mirrordirector.raspbian.org/raspbian/ jessie/main python-wheel all 0.24.0-1 [47.5 kB]
Fetched 1,144 kB in 2s (540 kB/s)
Selecting previously unselected package python-chardet.
(Reading database ... 51255 files and directories currently installed.)
Preparing to unpack .../python-chardet_2.3.0-1_all.deb ...
Unpacking python-chardet (2.3.0-1) ...
Selecting previously unselected package python-colorama.
Preparing to unpack .../python-colorama_0.3.2-1_all.deb ...
Unpacking python-colorama (0.3.2-1) ...
Selecting previously unselected package python-distlib.
Preparing to unpack .../python-distlib_0.1.9-1_all.deb ...
Unpacking python-distlib (0.1.9-1) ...
Selecting previously unselected package python-six.
Preparing to unpack .../python-six_1.8.0-1_all.deb ...
Unpacking python-six (1.8.0-1) ...
Selecting previously unselected package python-html5lib.
Preparing to unpack .../python-html5lib_0.999-3_all.deb ...
Unpacking python-html5lib (0.999-3) ...
Selecting previously unselected package python-openssl.
Preparing to unpack .../python-openssl_0.13.1-2_armhf.deb ...
Unpacking python-openssl (0.13.1-2) ...
Selecting previously unselected package python-ndg-httpsclient.
Preparing to unpack .../python-ndg-httpsclient_0.3.2-1_all.deb ...
Unpacking python-ndg-httpsclient (0.3.2-1) ...
Selecting previously unselected package python-urllib3.
Preparing to unpack .../python-urllib3_1.9.1-3_all.deb ...
Unpacking python-urllib3 (1.9.1-3) ...
Selecting previously unselected package python-requests.
Preparing to unpack .../python-requests_2.4.3-6_all.deb ...
Unpacking python-requests (2.4.3-6) ...
Selecting previously unselected package python-setuptools.
Preparing to unpack .../python-setuptools_5.5.1-1_all.deb ...
Unpacking python-setuptools (5.5.1-1) ...
Selecting previously unselected package python-pip.
Preparing to unpack .../python-pip_1.5.6-5_all.deb ...
Unpacking python-pip (1.5.6-5) ...
Selecting previously unselected package python-pyasn1.
Preparing to unpack .../python-pyasn1_0.1.7-1_all.deb ...
Unpacking python-pyasn1 (0.1.7-1) ...
Selecting previously unselected package python-wheel.
Preparing to unpack .../python-wheel_0.24.0-1_all.deb ...
Unpacking python-wheel (0.24.0-1) ...
Processing triggers for man-db (2.7.0.2-5) ...
Setting up python-chardet (2.3.0-1) ...
Setting up python-colorama (0.3.2-1) ...
Setting up python-distlib (0.1.9-1) ...
Setting up python-six (1.8.0-1) ...
Setting up python-html5lib (0.999-3) ...
Setting up python-openssl (0.13.1-2) ...
Setting up python-ndg-httpsclient (0.3.2-1) ...
Setting up python-urllib3 (1.9.1-3) ...
Setting up python-requests (2.4.3-6) ...
Setting up python-setuptools (5.5.1-1) ...
Setting up python-pip (1.5.6-5) ...
Setting up python-pyasn1 (0.1.7-1) ...
Setting up python-wheel (0.24.0-1) ...
Processing triggers for python-support (1.0.15) ...

And finally (This is very important!!!) EDIT THE ~/miflora/demo.py file, you need to paste in the actual MAC address of your MiFlora which we got earlier using hcitool. But we can run hcitool again (you can only exit by pressing Ctrl+C)

pi@pi3pimatic:~ $ sudo hcitool lescan
LE Scan ...
C4:7C:8D:61:57:A0 (unknown)
C4:7C:8D:61:57:A0 Flower mate
^C

And we edit the demo code python file:

 pi@pi3pimatic:~ $ nano /home/pi/miflora/demo.py<>

You need to delete the MAC address already present there, and paste in your MiFlora's MAC address (in my case it's C4:7C:8D:61:57:A0)

Aaaand finally:
pi@pi3pimatic:~ $ sudo python3 /home/pi/miflora/demo.py
Getting data from Mi Flora
FW: 2.6.2
Name: Flower mate
Temperature: 28.9
Moisture: 2
Light: 112
Conductivity: 0
Battery: 100

If you run this repeatedly, you’ll see that you get the same data up to 5 minutes. That’s because the MiFlora doesn’t make a measurement on it’s sensors continuously, but polls them once every 5 minutes. This is probably to save battery power and also to avoid oxidation of prongs which is exposed to water in the ground – it’s a common problem of soil humidity sensors.

Now that we can fetch the data to the Pi3, we need to tell Pimatic to display them.

Integration in pimatic

We need the pimatic-cron, pimatic-shell-execute, pimatic-log-reader plugins to be installed for this, so do that first.

It needs to look like this in the config:

"plugins": [
    {
      "plugin": "cron"
    },
    {
      "plugin": "shell-execute",
      "active": true
    },
    {
	  "plugin": "log-reader",
      "active": true,
	  "debug": true
    }
  ]

Then create the rule which will run the script every 10 minutes (there is no point to run it more freqently, as the MiFlora itself refreshes it’s data every 5 minutes only). The rule uses the cron plugin and the shell-execute plugin, and overwrites the contents of the “/home/pi/miflora/miflora-pimatic.log” file, so it will always contain only the latest data.

In the pimatic web GUI you can set it up like this:

IF:
every 10 minutes
THEN:
execute "/home/pi/miflora/demo.py"

It will look like this in the config file:

"rules": [
    {
      "id": "poll-miflora",
      "name": "Poll MiFlora",
      "rule": "when every 10 minutes then execute \"python3 /home/pi/miflora/demo.py > /home/pi/miflora/miflora-pimatic.log\"",
      "active": true,
      "logging": false
    }
  ]

Then we will create a LogWatcher device which will keep an eye on the /home/pi/miflora/miflora-pimatic.log file, and display the 5 important variables: Temperature, Moisture, Light, Conductivity, Battery.

The device config looks like this:

"devices": [
     {
      "file": "/home/pi/miflora/miflora-pimatic.log",
      "attributes": [
        {
          "name": "Temperature",
          "type": "number",
          "unit": "°C"
        },
        {
          "name": "Moisture",
          "type": "number",
          "unit": "%"
        },
        {
          "name": "Light",
          "type": "number",
          "unit": "lux"
        },
        {
          "name": "Conductivity",
          "type": "number",
          "unit": "us/cm"
        },
        {
          "name": "Battery",
          "type": "number",
          "unit": "%"
        }
      ],
      "lines": [
        {
          "match": "Temperature: (.+)",
          "predicate": "miflora.temperature",
          "Temperature": "$1"
        },
        {
          "match": "Moisture: (.+)",
          "predicate": "miflora.moisture",
          "Moisture": "$1"
        },
        {
          "match": "Light: (.+)",
          "predicate": "miflora.light",
          "Light": "$1"
        },
        {
          "match": "Conductivity: (.+)",
          "predicate": "miflora.conductivity",
          "Conductivity": "$1"
        },
        {
          "match": "Battery: (.+)",
          "predicate": "miflora.battery",
          "Battery": "$1"
        }
      ],
      "xAttributeOptions": [],
      "id": "miflora",
      "name": "MiFlora",
      "class": "LogWatcher"
    }
]

The LogWatcher device will then update the data on the Pimatic frontend.

That is all!

You need to stick the sensor into the soil of the plant you wish to monitor (the soil level must be at the point where the prongs of the sensor split into two).

I also have set up push notifications using the pimatic-pushbullet plugin which alert me to water the plant if the humidity goes below a certain percentage (I’m testing the ideal threshold now).

Future enhancement:
I have small water pump which I’ll integrate to pimatic as well, and with a rule I’ll turn it on to water the plant.

There is an ongoing discussion on the pimatic forums about the MiFlora sensor, please feel free to join and comment if you’ve liked this tutorial.

All images are a courtesy of Xiaomi or my own screenshots.