My balcony plants hate me. They complain about too little water with hanging leaves and discountenance me and my gardening skills in general. Even a robot could give them more love. So I built one.
I found a bluetooth-controlled gardening pump on Aliexpress for 25.41€ incl shipping. After hooking up the included drip irrigation hoses to my plants I downloaded the Dripping Pro companion app and set it to drip 100ml per plant, every hour for twelve hours per day (12 is the maximum number of slots per day). I was really satified with how really well this setup works and can absolutely recommend it.
The only thing I was not perfectly happy about was that the pump would water the plants in the same fashion every day, completely disregarding how sunny or rainy the day is. I figured that if I found a way to control the pump directly I could build a system that takes the weather into account. My hope was that it would (1) not completely drown the plants when they need little water and (2) save me from refilling the water tank by using only as much water as needed.
Scouting out a way inside
My first step to complete pump control was to find out the actual manufacturer’s Facebook page and ask about any API documentation or developer resources. In another project I had a great experience getting the latest firmware tools for a thermal printer through a manufacturers Facebook messenger. Unfortunately Kamoer failed to respond :/ The next alternative was to reverse engineer the pump’s Bluetooth communication – which sounded like a fun thing to do.
Using bluetoothctl
or gatttool
on a computer and BLE scanner on my phone, I could find the device and its mac as well as its BLE services and characteristics. It turns out most of these are pretty useless: There is a Device Information service UUID, but it gives results like Firmware version “Firmware Revision” and Model “Model Number”.
I strongly suspect that the first custom service by the device is some example code that the manufacturer didn’t bother to remove. You can see the characteristics with Descriptors like “Characteristic 3”, all different combinations of R/W/N properties, and values 0x01
, 0x02
, 0x03
, 0x04
, and 0x0102030405
.
The second custom service looks more promising. The descriptor reads “Key Press State”, which is funny, because my pump doesn’t even have a button. I try to write a 0x01 to the characteristics, but nothing happens. It seems the app talks via some protocol with the pump.
When you search the internet for the 0000ffe0-0000-1000-8000-00805f9b34fb
service UUID, it turns out that it is the UUID that almost all iBeacon tag manufacturers use. It likely comes from a TI devkit example with a “Simple Keys Service (0xFFE0)” that this application builds on. Let’s try to find out more about what messages to send (and to which characteristics) by capturing the packets that the original app is sending.
Bluetooth packet sniffing
It is possible to sniff BLE traffic with special hardware like Ubertooth or an nRF devkit, but I don’t have any of those. Instead, I will try to enable recording of Android’s Bluetooth HCI logs while using app. Enabling the logging option in developer options is not difficult (make sure to reboot after), but extracting the logs proved a little more involved. I got it working on my Galaxy S8 with adb:adb shell dumpsys bluetooth_manager ; adb bugreport > BUG_REPORT.txt
It takes a minutes to complete, but generates a bugreport-2019-08-27-18-49-17.zip
that contains a file FS/data/log/bt/btsnoop_hci.log
which you can open with Wireshark.
The log contains a lot of data, which is difficult to parse visually. Using a display filter bthci_acl.dst.bd_addr == 90:E2:02:9C:DB:50
I can focus on the packets sent from the phone to the pump. Interestingly the app always tries to write request handle 0x37, to which the device responds with application error 0x80. Maybe it’s for a different model? After each write request follows a write command. It’s still a little difficult to switch between packet views, so I export the write command packages in plaintext format into a word processor.
Extracting Meaning from data
First, I did a bunch of regex search/replace to get only timestamps and packet data. The packet timestamps correspond to how I used the app (Open, correct one automatic-watering volume slot value, switch to manual control tab, start/stop twice). I colorized the lines based on my hypothesis for their meaning.
It turns out the app does write to the “Key Press State” characteristic like I tried earlier, but it seems like it sends three rather complex 32 byte packets (In the document I removed the preamble and 12 trailing zero bytes) to start and another one to stop.
Crafting a dripper-control library
To confirm my hypothesis, I started an IPython shell, import gatt and jotted down a quick and dirty script. It worked! Celebration! That was fun!
I condensed the whole thing into a library which makes it a little easier to use: After a pip3 install btdripper
you can run:
import btdripper dripper = btdripper.BtDripper() dripper.on() dripper.off()
Programmatic access to the pump opens up new possibilities and a path to creating the best 25€ balcony irrigation system ever to reside on my balcony.
In Part 2 of this story I will probably fight weather APIs and Linux Crontabs. All for my gardening machine of loving grace.
Alex
2019-09-25 — 00:56
Hey, nice work. Is there any way to integrate it in to HASS? Thanks
Laurenz
2019-09-25 — 08:55
Thanks! Not yet (Hence “Part 1” ;)), but chances are high I will get around to build it some time in the future.
Sergey
2019-12-12 — 22:56
Do you have any news about ths project?
Laurenz
2020-03-27 — 21:00
Sorry, I never got around to setting up all the Home Assistant things 🙁 By now I actually moved and the irrigation pump is somewhere in my basement…
leonid
2020-03-09 — 21:31
Hello, can you maybe write me? telegram @neoleads or email janvarry at gmail dot com
i did almost same thing, but got some info about modbus communication inside this device)
leonid
2020-03-27 — 19:16
Hello.
I do some research and with terminal “ble terminal” for android, i got info:
dripping pro works on modbus protocol.. but for easier way to get info:
open ble terminal
connect grow_c
write msg hex:01 03 30 01 00 07 5A C8
you got response like:
01 03 0E 12 0A 01 FF 88 88 00 02 00 00 06 AA 42 31 59 C1
___
120A -this is 4618 how much water still exist…
01 Slave address 0x01 (1)
03 Function code 0x03 (3) – Read Holding Registers
0E Byte count 0x0E (14)
Register value
0x120A (4618), 0x01FF (511), 0x8888 (34952), 0x0002 (2), 0x0000 (0), 0x06AA (1706), 0x4231 (16945)
59 C1 CRC 0x59C1 (22977)
leonid
2020-03-27 — 19:21
01 03 30 08 00 07 8A CA
right query
Laurenz
2020-03-27 — 21:03
That’s awesome! Feel free to extend the code in https://github.com/lalten/btdripper 🙂
leonid
2020-09-10 — 06:29
hehehe….
for linux, values: total liquid, current liquid, est days 🙂
import commands
import pexpect
import struct
import time
import sys
drink = 260
IMU_MAC_ADDRESS = “A8:10:87:69:3B:D3”
status,output = commands.getstatusoutput(“hciconfig hci0 down;hciconfig hci0 up”)
#print output
if __name__ == ‘__main__’:
gatt = pexpect.spawn(“timeout 5 gatttool -b ” + IMU_MAC_ADDRESS + ” -I”)
gatt.sendline(“connect”)
gatt.expect(“Connection successful”)
gatt.sendline(“char-write-req 0x0036 0103300800078ACA”)
gatt.expect(“0x0036 value:”)
gatt.expect(” \r\n”)
data = (gatt.before).decode(‘UTF-8’).replace(” “, “”)
data1 = (data.upper())
total = int((data1[14:18]),16)
ost = int((data1[18:22]),16)
print (“Total: ” + str(total))
print (“Inside: ” + str(ost))
print (“Remaining days: ” + str(ost/drink))
leonid
2020-09-10 — 06:30
Total: 4500
Inside: 4060
Remaining days: 15
alex
2020-04-07 — 00:09
hey did you guys made it work with home assistant? whould be nice to see how many water is left in the bottles etc many thanks on advance !!
alex
2020-04-09 — 22:11
anyone using in home assistant?
leonid
2021-07-09 — 02:03
its need to be connected over ifttt and module like a esp32 wroom with ble and wifi