Xiaomi Mi Flora Plant Sensor MQTT Client/Daemon

Hey @horschte! Nice one!

I’m seeing your “Ich brauch Dünger” and would be interested about how you decided for a specific value. I found that different plants and different soil produce varying readings/demands. I’m still unsure what to recommended in that regard. Best!

i just take the individually min/max values from the miaflora app for my flowers/plants and create some rules.
i know that these values are not really accurately.
but its enough if my flowers/plants stay alive…first time in my life :smiley:

1 Like

I see. I never really used the official app but I have similar data via persistence, influxdb and grafana.

grafik

1 Like

@horschte, how did you create these kind of charts in the basic UI? My charts are only one column width and not so polished… it seems that you didn’t used the standard Chart item?

i use influx db and grafana.

First of all: Big kudos to @ThomDietrich for assembling all the parts and this fantastic tutorial. It worked from the first moment on.

Setting up the miflora environment was to achieve a brief overview over the status of my plants and answer the main question: Do i need to water and /or fertilize a plant?

Based on this, i created a very simple status view in the sitemap.

Below you find all the items, sitemaps, rules and so on. I did not translate the labels because from the item name the usage should be clear.

Items and Groups
Based on my group concept all plats are organized in serveral groups

Group Plants "Planzen" <edit_settings> (Context)
    
     Group PlantThings "Planzen" <edit_settings> (Plants)
        
    Group PlantMonitoring "Planzendaten" <edit_settings> (Plants)
        Group PlantMoisture     "Feuchtigkeit" <edit_settings> (PlantMonitoring, Charting)
        Group PlantConductivity "Düngung" <edit_settings> (PlantMonitoring, Charting)
        Group PlantTemperature  "Temperatur" <edit_settings> (PlantMonitoring, Charting)
        Group PlantBrightness   "Helligkeit" <edit_settings> (PlantMonitoring, Charting)
        Group PlantStatus       "Status" <edit_settings> (PlantMonitoring)

The items for each plant are structured the same way. I use the botanical name für the item.

//----------------------------------------------------------------------------------------------------------------------------------------------------
//
//    MiFlora sensor at Zyperngras C4:7C:8D:65:B5:7D
//
//    Required values
//        Temperature:    10,0 - 35,0°C
//        Conductivity:   350 - 2000µs/cm
//        Moisture:       15 - 60%
//
//----------------------------------------------------------------------------------------------------------------------------------------------------
Group GF_BR_CyperusAlternifolius "Zyperngras Schlafzimmer" <weather_pollen> (GF_Bedroom, PlantThings)
        
Number GF_BR_CyperusAlternifolius_Brightness "Helligkeit Zyperngras [%d Lux]" <weather_light_meter> (GF_BR_CyperusAlternifolius, PlantBrightness) {mqtt="<[mqtt:miflora/Zyperngras:state:JSONPATH($.light)]"}
Number GF_BR_CyperusAlternifolius_Temperature "Temperatur Zyperngras [%.1f °C]" <temp_soil> (GF_BR_CyperusAlternifolius, PlantTemperature) {mqtt="<[mqtt:miflora/Zyperngras:state:JSONPATH($.temperature)]"}
Number GF_BR_CyperusAlternifolius_Conductivity "Düngung Zyperngras [%d µS/cm]" <weather_humidity_soil> (GF_BR_CyperusAlternifolius, PlantConductivity) {mqtt="<[mqtt:miflora/Zyperngras:state:JSONPATH($.conductivity)]"}
Number GF_BR_CyperusAlternifolius_Moisture "Bodenfeuchte Zyperngras [%d %%]" <weather_humidity_soil> (GF_BR_CyperusAlternifolius, PlantMoisture) {mqtt="<[mqtt:miflora/Zyperngras:state:JSONPATH($.moisture)]"}
Number GF_BR_CyperusAlternifolius_BatteryLevel "Batterie Zyperngras [%d %%]" <measure_battery> (GF_BR_CyperusAlternifolius, BatterieLevel) {mqtt="<[mqtt:miflora/Zyperngras:state:JSONPATH($.battery)]"}
Switch GF_BR_CyperusAlternifolius_Battery "Batterie Zyperngras [MAP(battery.map):%s]" <measure_battery_50> (GF_BR_CyperusAlternifolius, Batteries) {mqtt="<[mqtt:miflora/Zyperngras:state:JS(battery2switch.js)]"}
Number GF_BR_CyperusAlternifolius_Status "Zyperngras [MAP(plantstatus.map):%s]" <weather_pollen> (GF_BR_CyperusAlternifolius, PlantStatus)

Transformations
As you see i use some transformations in the labels. you will find them below.

battery.map - Battery status human readable

ON=Leer
OFF=OK
NULL=N/A
-=N/A

plantstatus.map (Translated) - Battery status human readable

NULL=No Data
-=No Data
0=Perfect
1=Water
2=Fertilize
3=Water with fertilizer
4=Too damp
6=Fertilize & Too damp
8=overfertilized
9=Water & overfertilized
12=Too damp & overfertilized

battery2switch.js - Transform the battery level to a switch see battery.map

(function(i) {
    var json = JSON.parse(i);
    if(json.battery <= 10) return "ON"
    else return "OFF"
})(input)

Rules
The only rule at the moment. It calculates every hour the plant status.

//------------------------------------------------------------------------------------------------------------------------------------------------------------
//
//    Constants and variables
//
//------------------------------------------------------------------------------------------------------------------------------------------------------------

// Values are taken from the Miflora app
val Map<String, Double> PlantDataMap = newHashMap (
    "GF_KI_Chilli_Moisture_Min"                -> 15.0, "GF_KI_Chilli_Moisture_Max"               -> 60.0, "GF_KI_Chilli_Conductivity_Min"               -> 350.0, "GF_KI_Chilli_Conductivity_Max"               -> 2000.0,
    "GF_LR_DypsisLutescens_Moisture_Min"       -> 15.0, "GF_LR_DypsisLutescens_Moisture_Max"      -> 60.0, "GF_LR_DypsisLutescens_Conductivity_Min"      -> 350.0, "GF_LR_DypsisLutescens_Conductivity_Max"      -> 2000.0,
    "GF_LR_PterisCretica_Moisture_Min"         -> 15.0, "GF_LR_PterisCretica_Moisture_Max"        -> 60.0, "GF_LR_PterisCretica_Conductivity_Min"        -> 350.0, "GF_LR_PterisCretica_Conductivity_Max"        -> 1500.0,
    "GF_LR_PhlebodiumAureum_Moisture_Min"      -> 15.0, "GF_LR_PhlebodiumAureum_Moisture_Max"     -> 60.0, "GF_LR_PhlebodiumAureum_Conductivity_Min"     -> 350.0, "GF_LR_PhlebodiumAureum_Conductivity_Max"     -> 2000.0,
    "GF_BR_CyperusAlternifolius_Moisture_Min"  -> 15.0, "GF_BR_CyperusAlternifolius_Moisture_Max" -> 60.0, "GF_BR_CyperusAlternifolius_Conductivity_Min" -> 350.0, "GF_BR_CyperusAlternifolius_Conductivity_Max" -> 2000.0,
    "GF_BR_CalatheaZebrina_Moisture_Min"       -> 15.0, "GF_BR_CalatheaZebrina_Moisture_Max"      -> 65.0, "GF_BR_CalatheaZebrina_Conductivity_Min"      -> 350.0, "GF_BR_CalatheaZebrina_Conductivity_Max"      -> 1000.0
)

//------------------------------------------------------------------------------------------------------------------------------------------------------------
//
//    Rule: Check the plant status watering and fertilisation every hour and update the status flag
//
//------------------------------------------------------------------------------------------------------------------------------------------------------------
rule "Check Plant Status"
when
    Time cron "0 0 * ? * *" //every Hour
then
    
    PlantThings.members.forEach[ Plant |
    
        var ActualMoisture = PlantMoisture.members.filter[ pm | pm.name == Plant.name+"_Moisture"].head.state as DecimalType
        var ActualConductivity = PlantConductivity.members.filter[ pm | pm.name == Plant.name+"_Conductivity"].head.state as DecimalType
        var ActualStatus = 0

        if (ActualMoisture <= PlantDataMap.get(Plant.name+"_Moisture_Min")) {
            ActualStatus = ActualStatus + 1
        }

        if (ActualMoisture > PlantDataMap.get(Plant.name+"_Moisture_Max")) {
            ActualStatus = ActualStatus + 4
        }

        if (ActualConductivity <= PlantDataMap.get(Plant.name+"_Conductivity_Min")) {
            ActualStatus = ActualStatus + 2
        }

        if (ActualConductivity > PlantDataMap.get(Plant.name+"_Conductivity_Max")) {
            ActualStatus = ActualStatus + 8
        }

        PlantStatus.members.filter[ pm | pm.name == Plant.name+"_Status"].head.postUpdate(ActualStatus)

        logInfo("rule.CheckPlantStatus", "Update status of {} to {}", Plant.name, transform("MAP", "plantstatus.map", ActualStatus.toString))

    ]

end

Sitemap
And finaly the sitemap. My plant are organized by room, so the sitemap is. Translated not alle the Strings :slight_smile:

Text label="Plant overview" icon="weather_pollen" {
        Frame label="Livingroom" {
            Text item=GF_LR_DypsisLutescens_Status label="Goldfruchtpalme [MAP(plantstatus.map):%s]" icon="weather_pollen" valuecolor=[0="green", !=0="red"]
            Text item=GF_LR_PterisCretica_Status label="Saumfarn [MAP(plantstatus.map):%s]" icon="weather_pollen" valuecolor=[0="green", !=0="red"]
            Text item=GF_LR_PhlebodiumAureum_Status label="Goldtüpfelfarn [MAP(plantstatus.map):%s]" icon="weather_pollen" valuecolor=[0="green", !=0="red"]
        }
        Frame label="Kitchen" {
            Text item=GF_KI_Chilli_Status label="Chilli [MAP(plantstatus.map):%s]" icon="weather_pollen" valuecolor=[0="green", !=0="red"]
        }
        Frame label="Bedroom" {
            Text item=GF_BR_CyperusAlternifolius_Status label="Zyperngras [MAP(plantstatus.map):%s]" icon="weather_pollen" valuecolor=[0="green", !=0="red"]
            Text item=GF_BR_CalatheaZebrina_Status label="Zebramarante [MAP(plantstatus.map):%s]" icon="weather_pollen" valuecolor=[0="green", !=0="red"]
        }
}

The next step is to write a rule that once a day sends out the status of the plants that need “help”.

Any comments and suggestions are welcome.

@ThomDietrich It would be great if you could integrate the Daemon into the next release of openhabian.

Thomas

6 Likes

wow, impressive work @Dibbler42 , could we add when it got the latest status update aswell somehow?

Goldfruchpalme(18:30:00 19.11.2016) Optimal or
Goldfruchpalme(10 minutes ago) Optimal 

I added a group to all my plants:
Group Group_Moisture "Mi Flora Soil Moisture elements" (gAll)

then I just created a simple rule :

val Boolean emailSent = true

rule "Water plants"
when
	Item Group_Moisture changed
then
	if (Group_Moisture.state <10 && !emailSent){
	    sendMail("xxx@gmail.com","Plant","Water plants")
    }
end

However I have a problem with two of my sensors:

python3 /opt/miflora-mqtt-daemon/miflora-mqtt-daemon.py

Xiaomi Mi Flora Plant Sensor MQTT Client/Daemon
Source: https://github.com/ThomDietrich/miflora-mqtt-daemon

[2017-11-01 00:04:10] Connecting to MQTT broker ...
[2017-11-01 00:04:10] MQTT connection established

Adding sensor to device list and testing connection ...
Name:          "Sensor1"
Internal name: "sensor1"
Device name:   "Flower care"
MAC address:   C4:7C:8D:61:A5:7D
Firmware:      2.7.0
[2017-11-01 00:04:13] Initial connection to Mi Flora sensor "Sensor1" (C4:7C:8D:61:A5:7D) successful

Adding sensor to device list and testing connection ...
Name:          "Sensor2"
connect: Device or resource busy (16)
connect: Device or resource busy (16)
connect error: Connection refused (111)
connect: Device or resource busy (16)
connect: Device or resource busy (16)
[2017-11-01 00:04:43] Failed to retrieve data from Mi Flora sensor "Sensor2" (C4:7C:8D:62:50:EC) during initial connection.

Adding sensor to device list and testing connection ...
Name:          "Sensor3"
Internal name: "sensor3"
Device name:   "Flower mate"
MAC address:   C4:7C:8D:62:51:52
Firmware:      2.6.2
[2017-11-01 00:04:49] Initial connection to Mi Flora sensor "Sensor3" (C4:7C:8D:62:51:52) successful

Adding sensor to device list and testing connection ...
Name:          "Sensor4"
connect error: Transport endpoint is not connected (107)
connect error: Transport endpoint is not connected (107)
Internal name: "sensor4"
Device name:   "Flower mate"
MAC address:   C4:7C:8D:62:51:42
Firmware:      2.6.2
[2017-11-01 00:05:26] Initial connection to Mi Flora sensor "Sensor4" (C4:7C:8D:62:51:42) successful

Adding sensor to device list and testing connection ...
Name:          "Sensor5"
connect error: Device or resource busy (16)
[2017-11-01 00:06:57] Failed to retrieve data from Mi Flora sensor "Sensor5" (C4:7C:8D:65:B8:BA) during initial connection.

Adding sensor to device list and testing connection ...
Name:          "Sensor6"
connect error: Too many levels of symbolic links (40)
connect error: Transport endpoint is not connected (107)
connect error: Transport endpoint is not connected (107)
connect error: Device or resource busy (16)
[2017-11-01 00:07:39] Failed to retrieve data from Mi Flora sensor "Sensor6" (C4:7C:8D:65:A8:A2) during initial connection.

what does :

connect: Device or resource busy (16)
connect error: Connection refused (111) 

connect error: Too many levels of symbolic links (40)
connect error: Transport endpoint is not connected (107)

Can I somehow add a second pi to increase the bluetooth range or how does people normally get around connection issue?

v.2.7.1 also works @ThomDietrich

@ThomDietrich
Tanks a lot for your work, this will most likely replace my Koubachi!
I set up an old Rasperry Pi with your daemon and it runs pretty smooth. Didn’t have any prior MQTT experience but setting it up for these sensors was easy enough.
Just got one window between the outdoor sensor and the RPI that’s too much. Got to figure out a good position for sensors / RPI for them to work reliably.

Using the systemd service, is there any log written? I can run “sudo service miflora status” but doesn’t give me a complete picture. If there’s a log written with all successful and unsuccesful attempts of contacting the sensors, I haven’t found it yet.

Just a small update to my status display. I am really to lazy to check the status display every day. To overcome this i added a rule that uses pushover to inform me once a day about the plant status. From my earlier post i have changed the PlantDataMap by adding name and location of the plant as well as chanhing the items names.

Here is the new rule

/ Values are taken from the Miflora app
val Map<String, Double> PlantDataMap = newHashMap (
    "MO_Plant001_Moisture_Min" -> 15.0, "MO_Plant001_Moisture_Max" -> 60.0, "MO_Plant001_Conductivity_Min" -> 350.0, "MO_Plant001_Conductivity_Max" -> 2000.0, "MO_Plant001_Name" -> "Chilli",          "MO_Plant001_Room" -> "Küche",
    "MO_Plant002_Moisture_Min" -> 15.0, "MO_Plant002_Moisture_Max" -> 60.0, "MO_Plant002_Conductivity_Min" -> 350.0, "MO_Plant002_Conductivity_Max" -> 2000.0, "MO_Plant002_Name" -> "Olive",           "MO_Plant002_Room" -> "Büro",
    "MO_Plant003_Moisture_Min" -> 15.0, "MO_Plant003_Moisture_Max" -> 65.0, "MO_Plant003_Conductivity_Min" -> 350.0, "MO_Plant003_Conductivity_Max" -> 1000.0, "MO_Plant003_Name" -> "Zebramarante",    "MO_Plant003_Room" -> "Schlafzimmer",
    "MO_Plant004_Moisture_Min" -> 15.0, "MO_Plant004_Moisture_Max" -> 60.0, "MO_Plant004_Conductivity_Min" -> 350.0, "MO_Plant004_Conductivity_Max" -> 2000.0, "MO_Plant004_Name" -> "Zyperngras",      "MO_Plant004_Room" -> "Schlafzimmer",
    "MO_Plant005_Moisture_Min" -> 15.0, "MO_Plant005_Moisture_Max" -> 60.0, "MO_Plant005_Conductivity_Min" -> 350.0, "MO_Plant005_Conductivity_Max" -> 2000.0, "MO_Plant005_Name" -> "Goldtüpfelfarn",  "MO_Plant005_Room" -> "Wohnzimmer",
    "MO_Plant006_Moisture_Min" -> 15.0, "MO_Plant006_Moisture_Max" -> 60.0, "MO_Plant006_Conductivity_Min" -> 350.0, "MO_Plant006_Conductivity_Max" -> 1500.0, "MO_Plant006_Name" -> "Saumfarn",        "MO_Plant006_Room" -> "Wohnzimmer",
    "MO_Plant007_Moisture_Min" -> 15.0, "MO_Plant007_Moisture_Max" -> 60.0, "MO_Plant007_Conductivity_Min" -> 350.0, "MO_Plant007_Conductivity_Max" -> 2000.0, "MO_Plant007_Name" -> "Goldfruchtpalme", "MO_Plant007_Room" -> "Wohnzimmer"
)

//------------------------------------------------------------------------------------------------------------------------------------------------------------
//
//    Rule: Every day at 16:00 create a pushover message and send it out
//
//------------------------------------------------------------------------------------------------------------------------------------------------------------
rule "Publish Plant Status"
when
    Time cron "0 0 16 ? * *"
then

    var String Message = "-> Pflanzenstatus <-\n"

    PlantThings.members.forEach[ Plant |

        var ActualStatus = PlantStatus.members.filter[ pm | pm.name == Plant.name+"_Status"].head.state as DecimalType
        var ActualName = PlantDataMap.get(Plant.name + "_Name")

        if ((ActualStatus == 1) || (ActualStatus == 2) || (ActualStatus == 3) || (ActualStatus == 6) || (ActualStatus == 9)) {
            Message = Message + ActualName + " -> " + transform("MAP", "plantstatus.map", ActualStatus.toString) + "\n"
        }
    ]
    Message = Message + "-> Ende <-"

    pushover(Message, "Disorganiser")
    pushover(Message, "Froschzilla")

end

pushover could be replaced by any other service.

Things todo:
1.) Rework the status message to be more informative - Suggestions are welcome
2.) Sort plants by room to optimize the care route - Help is welcome, because at the moment if have no idea :frowning:

That’s it for today

Thomas

3 Likes

Just a WAF (Woman Acceptance Factor) update. Include a flag that suppresses the message if there is nothing to do.

New Rule

//------------------------------------------------------------------------------------------------------------------------------------------------------------
//
//    Rule: Every day at 16:00 create a pushover message and send it out
//
//------------------------------------------------------------------------------------------------------------------------------------------------------------
rule "Publish Plant Status"
when
    Time cron "0 0 16 ? * *"
then

    var Boolean ThingsToDo = False
    var String Message = "-> Pflanzenstatus <-\n"

    PlantThings.members.forEach[ Plant |

        var ActualStatus = PlantStatus.members.filter[ pm | pm.name == Plant.name+"_Status"].head.state as DecimalType
        var ActualName = PlantDataMap.get(Plant.name + "_Name")

        if ((ActualStatus == 1) || (ActualStatus == 2) || (ActualStatus == 3) || (ActualStatus == 6) || (ActualStatus == 9)) {
            Message = Message + ActualName + " -> " + transform("MAP", "plantstatus.map", ActualStatus.toString) + "\n"
            ThingsToDo = True
        }
    ]
    Message = Message + "-> Ende <-"

    if (ThingsToDo) {
        pushover(Message, "Disorganiser")
        pushover(Message, "Froschzilla")
    }

end

This flag is not tested.

Thomas

3 Likes

Hallo Patrick,
no there is not. Simply didn’t see the need for it. If you’D be interested we could talk about an improvement of course.
I was thinking about a related issue and plan to implement a better usage of the status message, check: https://github.com/ThomDietrich/miflora-mqtt-daemon/issues/16

So instead of this:

I’d remove the overly redundant error messages and update the “Status” line with the success rate of all plants. What do you think?

Quick question for my understanding…

First, the daemon can be installed in the background on my rpi3 with running openhab2?
Second, if i will not reach the plant sensor with my rpi3 (OH) i can setup rpi0w as a bridge which will send via mqtt the data to my rpi3(oh) mqtt broker?! :thinking::thinking:

Exactly. The RPi0w (or any other device) will act as yet another miflora data collector. Both daemons will run independently and publish their data to your MQTT broker. In the daemon configurations you’ll only set up those sensors which are supposed to be queried by this daemon.

1 Like

Should we prepare an image for the bridge to make a super easy installation?
Is there a smart way to synchronize the /opt/miflora-mqtt-daemon/config.ini file lets say every midnight between bridge(copy) and the openhabian master rpi?

You mean like openHABian :smiley:
Adding the daemon as an optional component is on my todo list! Sadly I am packed with other stuff. If you’d like to help, that would be amazing! @Multisaft7 @skatun @Dibbler42 anyone?

1 Like

I would by i not own a Flora Sensor since now…will take a look at the coming ads in the next day and will try to get one…

Did you get yours from aliexpress?

Yes. https://www.aliexpress.com/wholesale?SearchText=xiaomi+mi+flora+plant+sensor

Back then there were no different versions. Now there is an international Version which is basically more expensive but can be paired with the app outside of China. If you (like me) will not use the app, you can ignore that and go cheaper.

Installation of the daemon is pretty easy. Maybe I’ll add it to openHABian tonight.

1 Like

Thanks for the feedback. Yes, being able to call status on the daemon and get an overview of all plants would be awesome.
But no hurry. By now, I’ve integrated two sensors in my setup and I also added a “Last Update” time to each one that gets updated as soon as one of the values changed (and light intensity basically updates each time).
Set it to 15min and saw now, that both work flawlessly every 15min, so I expect to see almost 100% success rate in the status message. :smiley:

Here we go :smiley:

1 Like