Read Temperature and Huminidy from MHO-C401

MHO-C401 is new (2020) MMC E-Ink Screen Smart #Bluetooth Thermometer Hygrometer BT2.0 Temperature Humidity Sensor from Xiaomi. You can order yours on Gearbest or Aliexpress.

MHO-C401

Searching for sensor

Every Bluetooth Low Energy (BLE) device have unique MAC address - you can search this address with hcitool

hcitool is Linux tool for monitoring and configuring Bluetooth devices. It is aptly named hcitool as it communicates via a common HCI (Host Controller Interface port to your bluetooth devices. You can utilize the utility to scan for devices and send commands/data for standard Bluetooth and Bluetooth Low Energy.

First check, if your hcitool can see your device with hcitool dev command, then you can start lescan for other devices arroud.

sudo hcitool lescan

You will get output similar to this one:

49:01:7D:E8:21:CF (unknown)
4D:E8:5B:D3:56:7E (unknown)
5C:6D:23:6F:57:14 (unknown)
...
A4:C1:38:4B:E8:FF (unknown)
A4:C1:38:4B:B7:FF MHO-C401
...

As you can see from list my MHO-C401 have A4:C1:38:4B:B7:FF MAC address.

Lets read some data!

We will use a Python to read the data from BLE - there are some great libraries for that, like bluepy. Bluepy provide a comprehensive API to allow access to Bluetooth Low Energy devices.

sudo apt-get install python3-pip libglib2.0-dev
sudo pip3 install bluepy

Each BLE devices provide Services and Characteristics. Services are used to break data up into logic entities, and contain specific chunks of data called characteristics. A service can have one or more characteristics, and each service distinguishes itself from other services by means of a unique numeric ID called a UUID, which can be either 16-bit (for officially adopted BLE Services) or 128-bit (for custom services).

The Python code below will generate a list of all the available services and characteristics on the our BLE device.

from bluepy import btle
device = btle.Peripheral()
try:
  print("Connecting to device...")
  device.connect("A4:C1:38:4B:B7:FF") # need change your MAC address here 
  for service in device.getServices():
    print(str(service))
    for ch in service.getCharacteristics():
        if ch.supportsRead():
          	print(" {}".format(ch))
            print(" > UUID: ", ch.uuid)
            print(" > HANDLE: ", hex(ch.getHandle()))
            print(" > SUPPORTS: ", ch.propertiesToString())
            if (ch.supportsRead()):
              try:
                print(" > RESULTS ", repr(ch.read()))
              except BTLEException as e:
                print(" > ERROR: ", e)
            print()
finally:
  device.disconnect()

Output will looks like that:

Connecting to device...
Service <uuid=Generic Access handleStart=1 handleEnd=7>
 Characteristic <Device Name>
 > UUID: 00002a00-0000-1000-8000-00805f9b34fb
 > HANDLE: 0x2
 > SUPPORTS: READ NOTIFY 
 > RESULTS  b'MHO-C401\x00'

 Characteristic <Appearance>
 > UUID: 00002a01-0000-1000-8000-00805f9b34fb
 > HANDLE: 0x4
 > SUPPORTS: READ 
 > RESULTS  b'\x00\x00'

 Characteristic <Peripheral Preferred Connection Parameters>
 > UUID: 00002a04-0000-1000-8000-00805f9b34fb
 > HANDLE: 0x6
 > SUPPORTS: READ 
 > RESULTS  b'\x14\x00(\x00\x00\x00\xe8\x03'

...

Then you can read first device characteristic, with follow code:

from bluepy import btle

device = btle.Peripheral()

try:
  device.connect("A4:C1:38:4B:B7:FF")

  # read by UUID
  deviceName = device.getCharacteristics(uuid="00002a00-0000-1000-8000-00805f9b34fb")[0].read()
  print("Device name: ", deviceName.decode('ascii'))

  # or by handle  
  print("Firmware: " , device.readCharacteristic(0x12).decode('ascii'))
  print("Hardware Revision: " , device.readCharacteristic(0x14).decode('ascii'))
  print("Manufacturer Name: " , device.readCharacteristic(0x18).decode('ascii'))

  # read battery level
  print("Battery level: {}%".format(ord(device.readCharacteristic(0x1b))))

  # read device units
  if (device.readCharacteristic(0x33) == b'\x00'):
    print('Units: °C')
  
  if (device.readCharacteristic(0x33) == b'\x01'):
    print('Units: °F')

finally:
  device.disconnect()

Reading temperature and humidity

For reading temperature and humidity you have to subscribe notifications for UUID = EBE0CCC1-7A0A-4B0C-8A1A-6FF2997DA3A6 - there are 3 bytes of data. Notifications are processed by creating a “delegate” object and registering it with the Peripheral.

from bluepy import btle
from datetime import datetime

device = btle.Peripheral()

class Delegate(btle.DefaultDelegate):
  def handleNotification(self, cHandle, data):    
    temperature_bytes = data[:2]
    humidity_bytes = data[2]
    temperature = int.from_bytes(temperature_bytes, byteorder="little") / 100.0
    humidity = humidity_bytes

    print("Tempterature: {}°C".format(temperature))
    print("    Humidity: {}%".format(humidity))
    print("        Time: {}".format(datetime.now().strftime("%H:%M:%S")))

try:  
  device.connect("A4:C1:38:4B:B7:FF")
  device.setDelegate(Delegate())
  ch = device.getCharacteristics(uuid="EBE0CCC1-7A0A-4B0C-8A1A-6FF2997DA3A6")[0]
  desc = ch.getDescriptors(forUUID=0x2902)[0]
  desc.write(0x01.to_bytes(2, byteorder="little"), withResponse=True)

  # waiting to notification
  while True:
    if not device.waitForNotifications(5.0):
      break

finally:
  device.disconnect()

Upper code will generate follow output:

Tempterature: 25.82°C
    Humidity: 49%
        Time: 13:32:09

Source codes https://github.com/OzzyCzech/MHO-C401

Sources

#iot