iPhone app for Xiaomi Plant Sensor

I’m working on a new project to create an integrated iPhone app for both my Xiaomi plant sensors and the Parrot Flower Power sensors I already have. I’m able to scan the area for all of the sensors, get the services they provide, read the characteristics of each service and then get the values they offer.

The data I get back is a bit confusing thus far, doesn’t match up to what the writer of this article found. Another article implies that one has to turn on real-time data to get anything but zeros. Anyone had success with this? I’ll post my project when it is worthy.

TIA

Janene

Have you had any luck with this?
You can look at the source code at https://github.com/open-homeautomation/miflora for some inspiration.
Its Linux only and depends on gatttool.

As i’m on windows i’m going to try to emulate the gatttool on windows, but the author of the code hasen’t responded to my request about the input and output expected from gatttool yet.
https://github.com/open-homeautomation/miflora/issues/19

I haven’t gotten my miflora yet but once i do ill see if i can read data from it with .net on Windows 10.

A pi zero W with bluetooth is only 10 dollars. Don’t want to put you off this project but sounds like a lot of work :slight_smile:

@robmarkcole yea i know its my backup, but i don’t want to move my HA installation from my windows machine, can i somehow forward the data from a pi zero w to my ha installation?

Also i would need a case, sd card, power supply so its more like 20 - 30 bucks :stuck_out_tongue:.

Yes just use MQTT (and a script on the pi) :slight_smile: Check Strategy for Remote RPi integration?

Use any old usb cable, then…

Iv managed to connect and read data from the sensor in .net, so far i have no idea on the format but that will have to be tomorrows problem.

Could some do me a few favors:
1. Copy the gatttool help my guess gatttool --help (tried to read the source code in the hope that it contained this information but no success)
2. Run gatttool --device=[MAC] --char-write-req -a 0x33 -n A01F --adapter=hci0 and copy the output. (only do this if you are running firmware_version >= 2.6.6, so if you are unsure skip this one)
3. Run gatttool --device=[MAC] --char-read -a 0x03 --adapter=hci0 and copy the output. (get name)
4. Run gatttool --device=[MAC] --char-read -a 0x35 --adapter=hci0 and copy the output. (get cache??)
5. Run gatttool --device=[MAC] --char-read -a 0x038 --adapter=hci0 and copy the output. (get firmware version and battery level)

It would help me alot!

Managed to run gatttool on a virtual machine but it lacks bluetooth support so i can’t run the rest of the commands.

Usage:
  gatttool [OPTION...]

Help Options:
  -h, --help                                Show help options
  --help-all                                Show all help options
  --help-gatt                               Show all GATT commands
  --help-params                             Show all Primary Services/Characteristics arguments
  --help-char-read-write                    Show all Characteristics Value/Descriptor Read/Write arguments

GATT commands
  --primary                                 Primary Service Discovery
  --characteristics                         Characteristics Discovery
  --char-read                               Characteristics Value/Descriptor Read
  --char-write                              Characteristics Value Write Without Response (Write Command)
  --char-write-req                          Characteristics Value Write (Write Request)
  --char-desc                               Characteristics Descriptor Discovery
  --listen                                  Listen for notifications and indications

Primary Services/Characteristics arguments
  -s, --start=0x0001                        Starting handle(optional)
  -e, --end=0xffff                          Ending handle(optional)
  -u, --uuid=0x1801                         UUID16 or UUID128(optional)

Characteristics Value/Descriptor Read/Write arguments
  -a, --handle=0x0001                       Read/Write characteristic by handle(required)
  -n, --value=0x0001                        Write characteristic value (required for write operation)

Application Options:
  -i, --adapter=hciX                        Specify local adapter interface
  -b, --device=MAC                          Specify remote Bluetooth address
  -t, --addr-type=[public | random]         Set LE address type. Default: public
  -m, --mtu=MTU                             Specify the MTU size
  -p, --psm=PSM                             Specify the PSM for GATT/ATT over BR/EDR
  -l, --sec-level=[low | medium | high]     Set security level. Default: low
  -I, --interactive                         Use interactive mode

Yes, I’ve had success :slight_smile:

Using the standard iOS BLE methods, I’m able to discover, connect and read them. I used the python program to help decode the data. Won’t help you on Windows, however as this is Mac/iOS/Swift. I also had to discover the UUID for each sensor, for each of my iOS devices, as it changes for each (iPad gets a different value than iPhone):

After doing the discover/connect dance, I use the following methods to extract the data. It does have to do the enable/disable realtime updates writing, but all seems to be working now.

static let kEnableRealtimeUUIDString =  "00001A00-0000-1000-8000-00805F9B34FB"
static let kSensorDataUUIDString =      "00001A01-0000-1000-8000-00805F9B34FB"
static let kBatteryDataUUIDString =     "00001A02-0000-1000-8000-00805F9B34FB"

var enableRealTimeCharacteristic: CBCharacteristic?
var sensorDataCharacteristic: CBCharacteristic?

struct ParameterInfo {
    var title = ""
    var startIndex = 0
    var multiplier = 0.0
    var unit = ""
    
    init (paramTitle: String, paramStart: Int,  paramMultiplier: Double, paramUnit: String) {
        title = ""
        startIndex = paramStart
        multiplier = paramMultiplier
        unit = paramUnit
    }
}

// Temp, Light, Moisture, Fertility
let parameterInfo = [ParameterInfo(paramTitle: "Temp" ,     paramStart: 0, paramMultiplier: 0.1, paramUnit: "C"),
                     ParameterInfo(paramTitle: "Light",     paramStart: 3, paramMultiplier: 1.0, paramUnit: ""),
                     ParameterInfo(paramTitle: "Moisture",  paramStart: 7, paramMultiplier: 1.0, paramUnit: "%"),
                     ParameterInfo(paramTitle: "Fertility", paramStart: 8, paramMultiplier: 1.0, paramUnit: ""),
                     ParameterInfo(paramTitle: "Battery",   paramStart: 0, paramMultiplier: 1.0, paramUnit: "")
    ]

//MARK: peripheral delegate methods
override func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
    for char in service.characteristics! {
//            print("GOT CHARACTERISTIC: \(char)")
        if char.uuid.uuidString == FlowerCarePeripheral.kEnableRealtimeUUIDString {
            if myService == nil {
               myService = service
            }
            
            enableRealTimeCharacteristic = char
            let data = NSData(bytes: [0xa0, 0x1f], length: 2) as Data
            peripheral.writeValue(data, for: enableRealTimeCharacteristic!, type: CBCharacteristicWriteType.withResponse)
            
        } else if char.uuid.uuidString == FlowerCarePeripheral.kSensorDataUUIDString {
            sensorDataCharacteristic = char
        } else if char.uuid.uuidString == FlowerCarePeripheral.kBatteryDataUUIDString {
            peripheral.readValue(for: char) // don't have to wait to read this one
        }
    }
}

func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
    
    if (error != nil) {
        print(error.debugDescription)
        return
    }
//        print("WROTE TO DEVICE")
     if sensorDataCharacteristic != nil {
         peripheral.readValue(for: sensorDataCharacteristic!)
     }
 }

func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
    
    if let data = characteristic.value {
        if data.count < 1 {return}

        if characteristic.uuid.uuidString == FlowerCarePeripheral.kSensorDataUUIDString {
            let temperature:Int16 = readInteger(data: data as NSData, start: parameterInfo[0].startIndex)
            sensorData.temperature = Int(temperature)
            
            let light:Int32 = readInteger(data: data as NSData, start: parameterInfo[1].startIndex)
            sensorData.light = Int(light)
            
            let moisture:Int8 = readInteger(data: data as NSData, start: parameterInfo[2].startIndex)
            sensorData.moisture = Int(moisture)
            
            let fertility:Int16 = readInteger(data: data as NSData, start: parameterInfo[3].startIndex)
            sensorData.fertility = Int(fertility)
            
        } else if characteristic.uuid.uuidString == FlowerCarePeripheral.kBatteryDataUUIDString {
            
            let battery: Int8 = readInteger(data: data as NSData, start: parameterInfo[4].startIndex)
            sensorData.battery = Int(battery)                
        }
    }
}
func readInteger<T : Integer>(data : NSData, start : Int) -> T {
    var d : T = 0
    data.getBytes(&d, range: NSRange(location: start, length: MemoryLayout<T>.size))
    return d
}

There are several free tools out there that helped, Bluetooth devices discovery to help identify all of the devices, etc. I used the Xiaomi app to match the UUIDs to devices by comparing the data received from each one. The app I have is clumsy, but I did get the Flower Power sensors integrated as well. It’ll be a project to tinker with for a while.

Janene

2 Likes

One thing i see you are using 3 Characteristics if i’m reading the code correctly?
But the python code uses 4? (0x33, 0x03, 0x035, 0x038)

@jpappas Hi Janene, i have finally managed to get those all needed Characteristics, the windows api was a bit confusing.
But it seems i only get dummy data when i read 00001A01-0000-1000-8000-00805F9B34FB i get AA BB CC DD EE FF 99 88 77 66 00 00 00 00 00 00 both after and before writing to 00001A00-0000-1000-8000-00805F9B34FB.

Do you know why this happens? (the value is the same no matter if its dry or wet)

(I have also tried with gatttool and got the same data so at least its consistent)

The firmware version on the device is 2.7.0 as read by 00001A02-0000-1000-8000-00805F9B34FB.

Any chance you could give me a sample output from kSensorDataUUID then i can loop all Characteristics and compare the values to your example?

@AnderssonPeter I had to wait to get the acknowledgement that the write was complete before trying to read from the device. That’s the didWriteValueFor function. Before that is complete, I would get the same as you see.

Here is some sample output for one of my devices, hope it helps:

CONNECTING TO Flower care : <CBPeripheral: 0x1700f8400, identifier = C167E2AA-D58F-4AAF-A5E6-88285787F460, name = Flower care, state = disconnected> : C167E2AA-D58F-4AAF-A5E6-88285787F460 : ["kCBAdvDataIsConnectable": 1, "kCBAdvDataLocalName": Flower care, "kCBAdvDataServiceData": {
FE95 = <71029800 55fc0562 8d7cc40d 0910025e 00>;
}, "kCBAdvDataServiceUUIDs": <__NSArrayM 0x17004f420>(
FE95
)
] : -89 dBm
CONNECTED TO Optional("Lg Dracaena") : Optional("Flower care") : C167E2AA-D58F-4AAF-A5E6-88285787F460
Discovered service <CBService: 0x174269180, isPrimary = YES, UUID = FE95>
Discovered service <CBService: 0x174269400, isPrimary = YES, UUID = FEF5>
Discovered service <CBService: 0x17027c780, isPrimary = YES, UUID = 00001204-0000-1000-8000-00805F9B34FB>
Discovered service <CBService: 0x17026bc00, isPrimary = YES, UUID = 00001206-0000-1000-8000-00805F9B34FB>
GOT CHARACTERISTIC: <CBCharacteristic: 0x1700a75c0, UUID = 0001, properties = 0x1A, value = (null), notifying = NO>
GOT CHARACTERISTIC: <CBCharacteristic: 0x1700ab280, UUID = 0002, properties = 0x2, value = (null), notifying = NO>
GOT CHARACTERISTIC: <CBCharacteristic: 0x1700ab340, UUID = 0004, properties = 0x12, value = (null), notifying = NO>
GOT CHARACTERISTIC: <CBCharacteristic: 0x1700ab3a0, UUID = 0007, properties = 0x8, value = (null), notifying = NO>
GOT CHARACTERISTIC: <CBCharacteristic: 0x1700ab400, UUID = 0010, properties = 0x8, value = (null), notifying = NO>
GOT CHARACTERISTIC: <CBCharacteristic: 0x1700ab460, UUID = 0013, properties = 0xA, value = (null), notifying = NO>
GOT CHARACTERISTIC: <CBCharacteristic: 0x1700ab4c0, UUID = 0014, properties = 0x2, value = (null), notifying = NO>
GOT CHARACTERISTIC: <CBCharacteristic: 0x1700ab520, UUID = 1001, properties = 0x10, value = (null), notifying = NO>
GOT CHARACTERISTIC: <CBCharacteristic: 0x1700ab5e0, UUID = 8082CAA8-41A6-4021-91C6-56F9B954CC34, properties = 0xA, value = (null), notifying = NO>
GOT CHARACTERISTIC: <CBCharacteristic: 0x1700ab640, UUID = 724249F0-5EC3-4B5F-8804-42345AF08651, properties = 0xA, value = (null), notifying = NO>
GOT CHARACTERISTIC: <CBCharacteristic: 0x1700ab6a0, UUID = 6C53DB25-47A1-45FE-A022-7C92FB334FD4, properties = 0x2, value = (null), notifying = NO>
GOT CHARACTERISTIC: <CBCharacteristic: 0x1700ab700, UUID = 9D84B9A3-000C-49D8-9183-855B673FDA31, properties = 0xA, value = (null), notifying = NO>
GOT CHARACTERISTIC: <CBCharacteristic: 0x1700ab760, UUID = 457871E8-D516-4CA1-9116-57D0B17B9CB2, properties = 0xE, value = (null), notifying = NO>
GOT CHARACTERISTIC: <CBCharacteristic: 0x1700ab7c0, UUID = 5F78DF94-798C-46F5-990A-B3EB6A065C88, properties = 0x12, value = (null), notifying = NO>
GOT CHARACTERISTIC: <CBCharacteristic: 0x1700ab580, UUID = 00001A00-0000-1000-8000-00805F9B34FB, properties = 0xA, value = (null), notifying = NO>
GOT CHARACTERISTIC: <CBCharacteristic: 0x1700ab820, UUID = 00001A01-0000-1000-8000-00805F9B34FB, properties = 0x1A, value = (null), notifying = NO>
GOT CHARACTERISTIC: <CBCharacteristic: 0x1700ab880, UUID = 00001A02-0000-1000-8000-00805F9B34FB, properties = 0x2, value = (null), notifying = NO>
GOT CHARACTERISTIC: <CBCharacteristic: 0x1700ab8e0, UUID = 00001A11-0000-1000-8000-00805F9B34FB, properties = 0x2, value = (null), notifying = NO>
GOT CHARACTERISTIC: <CBCharacteristic: 0x1700ab940, UUID = 00001A10-0000-1000-8000-00805F9B34FB, properties = 0x1A, value = (null), notifying = NO>
GOT CHARACTERISTIC: <CBCharacteristic: 0x1700ab9a0, UUID = 00001A12-0000-1000-8000-00805F9B34FB, properties = 0x2, value = (null), notifying = NO>
WROTE TO DEVICE
CHARACTERISTIC UPDATED: <CBCharacteristic: 0x1700ab880, UUID = 00001A02-0000-1000-8000-00805F9B34FB, properties = 0x2, value = <641e322e 392e32>, notifying = NO>
CHARACTERISTIC UPDATED: <CBCharacteristic: 0x1700ab820, UUID = 00001A01-0000-1000-8000-00805F9B34FB, properties = 0x1A, value = <cb00000b 0000001f 5e00023c 00fb349b>, notifying = NO>
DISCONNECTED!  Optional("Lg Dracaena") : Optional("Flower care") : C167E2AA-D58F-4AAF-A5E6-88285787F460

Thanks for your help i have it working now, i havent found a way to get the Handle under windows so i have to hard code that 0x## = uuid
But running using the following bat file works

gatttool --device=C4:7C:8D:62:D3:19 --char-read -a 0x03
gatttool --device=C4:7C:8D:62:D3:19 --char-read -a 0x038
gatttool --device=C4:7C:8D:62:D3:19 --char-write-req -a 0x33 -n A01F
gatttool --device=C4:7C:8D:62:D3:19 --char-read -a 0x35

output:

D:\Temp\gattool>gatttool --device=C4:7C:8D:62:D3:19 --char-read -a 0x03
Characteristic value/descriptor: 46 6C 6F 77 65 72 20 63 61 72 65

D:\Temp\gattool>gatttool --device=C4:7C:8D:62:D3:19 --char-read -a 0x038
Characteristic value/descriptor: 58 15 32 2E 37 2E 30

D:\Temp\gattool>gatttool --device=C4:7C:8D:62:D3:19 --char-write-req -a 0x33 -n A01F
Characteristic value was written successfully

D:\Temp\gattool>gatttool --device=C4:7C:8D:62:D3:19 --char-read -a 0x35
Characteristic value/descriptor: D7 00 00 76 09 00 00 49 B4 05 02 3C 00 FB 34 9B

I have a few other windows specific Home assistant projects i want to finish then ill publish this on github!
Again thanks for your help @jpappas