Add Boneco devices

If it helps, this is all of the values, I have not been able to successfully control my unit

FWIW, I was expecting the packet capture to start with some form of handshake, which stops the airwasher from disconnecting after a timeout.

Also I’m not sure what the values you shared mean. I hoped for a BLE service/characteristic UUIDs and a value, like here: https://youtu.be/_2RuJ5-pF5I

These are the values I sent from my phone to boneco, I connected my old android phone and I’m sniffing it.

i can share service uuid and uuid, but I expect they will be different always for different devices

Ther3 is some kind of a handshace, there is 3 writes without a value, I guess it is handshake, they have different uuid client charasteristics that are send, but I cant understand how to send them properly. In your tutorial there is also no information about handshake, how to do it.

What information would you need more to understand?

That values that I shared are so long sent to boneco and received from it, they hold some kind of constants and value inside and dynamic value by time as I understand

This is what I have, there is different handle for handshake I guess

1 Like

i can share service uuid and uuid, but I expect they will be different always for different devices

Nope, they are vendor-specific, but should not differ across devices of the same model. My unit has Service UUID fdce123410134120b9191dbb32a2d132 / Characteristic UUID fdce234610134120b9191dbb32a2d132, just like yours.

But: after reading up on BLE, it seems you just need to know the handle ID (which determines the service/characteristic/descriptor accessed) and the value written. Your doc only has the values, right?

I cant understand how to send them properly. In your tutorial there is also no information about handshake, how to do it.

Yep, every device is different, and it’s probably impossible to use the nRF Connect app here because of the quick disconnect :frowning:

Still, the idea is to first determine if the BLE communication looks easily reproducible, by performing the same simple sequence (e.g. connect - turn off - disconnect) repeatedly, and checking if it results in identical ATT requests (with the same handles/values).

If we’re lucky, the next step would be to try and reproduce the requests using a client you control. I think the highlighted row from your screenshot can be imitated like this:

gatttool -b xx:xx:xx:xx:xx:xx -t random -I 
 connect
 char-write-req 0x0021 0x01

…but I’m not sure about the best tool to do so: gatttool does not seem very well suitable for scripting and is being deprecated. Home Assistant seems to use bleak, if one is ready to write some Python.

1 Like

Hi guys. I have three BONECO H700 and also interested in integrating them into Home Assistant. Will this method work for BONECO?

Reverse Engineering Unsupported BT Devices and Integrating into HA

1 Like

Hi there! I have 2 devices H700 as well.Also interested in the integrations. I have an Android phone, tried to capture logs, but there are tons of noise in it.

Ahh, that sucks :frowning:

guys, no updates?

1 Like

Hmm, I’m just stuck, because I do not know how to communicate with the device properly, I was not able to connect and send the command :smiling_face_with_tear:

Android app does these steps every start:

  • connect to device
  • send auth request (using device key from pairing) and wait response (starts with bytes 040102)
  • read device name (just c-string)
  • read device state - byte array (little-endian encoding)
    • 0 - fan / fan+fan_mode
    • 1 - timer+history_active+lock+on_off_state
    • 2 - target humidity / nothing
    • 3 - change filter + change iss + clean + change water
    • 4-7 - filter data (counter or date)
    • 8-11 - iss data (counter or date)
    • 12-15 - clean data (counter or date)
    • 16 - on_off_timer_hours + clean mode support + on_off_timer_status
    • 17 - on_off_timer_minutes
    • 18 - min led brightness
    • 19 - max led brightness

some bytes are only for fan devices, some for P series. I’ve tested with W400 around 2 years ago

1 Like

Did anyone figure this out ?

I think I have made progress but not sure

I’m stuck on trying to bypass the handshake and stop it from disconnecting. Any suggestions?

Start with bleakclient.start_notify(CHARACTERISTIC_AUTH, callback) and def callback(sender: int, data: bytearray) -> None.
Your callback should check that sender == 0x026 and

  • Save “nonce” if len(data) == 20 and data[0] == 1
  • Save “auth level/step” from data[1] if data[0] == 4 and data[2] == 2
  • Save “device key” from data[3:19] if data[0:3] == b'\x06\x00\x00'

After getting “nonce” you should also call bleakclient.start_notify(CHARACTERISTIC_AUTH_AND_SERVICE, callback2) and start calling bleakclient.write_gatt_char(CHARACTERISTIC_AUTH_AND_SERVICE, some_data) for moving between auth states

callback2 just checks that sender == 0x029 and data[1] & 1 == 1

CHARACTERISTIC_AUTH = "fdce2347-1013-4120-b919-1dbb32a2d132"
CHARACTERISTIC_AUTH_AND_SERVICE = "fdce2348-1013-4120-b919-1dbb32a2d132"

Auth states: GOT_NONCE → CONFIRM_WAITING (here you press button on device) → CONFIRMED → GOT_DEVICE_KEY → AUTH_SUCCESS

im just getting this error

Started notifications for AUTH characteristic
callback invoked with sender: fdce2347-1013-4120-b919-1dbb32a2d132 (Handle: 38): Unknown, data: 0101c88b5474b03218a41249a5d396afcca20000
callback invoked with sender: fdce2347-1013-4120-b919-1dbb32a2d132 (Handle: 38): Unknown, data: 0400010000000000000000000000000000000000

with this script

import asyncio
from bleak import BleakClient

CHARACTERISTIC_AUTH = "fdce2347-1013-4120-b919-1dbb32a2d132"
CHARACTERISTIC_AUTH_AND_SERVICE = "fdce2348-1013-4120-b919-1dbb32a2d132"

DEVICE_MAC_ADDRESS = "E1:A6:44:55:39:CA"

# Global variables to store the data and state
nonce = None
auth_level_step = None
device_key = None
auth_state = "INIT"

def callback(sender: str, data: bytearray) -> None:
    global nonce, auth_level_step, device_key, auth_state
    print(f"callback invoked with sender: {sender}, data: {data.hex()}")

    if sender == CHARACTERISTIC_AUTH:
        if len(data) == 20 and data[0] == 1:
            nonce = data
            auth_state = "GOT_NONCE"
            print("Nonce saved:", nonce.hex())
            asyncio.create_task(start_notify_auth_and_service())
        elif data[0] == 4 and data[2] == 2:
            auth_level_step = data[1]
            auth_state = "CONFIRM_WAITING"
            print("Auth level/step saved:", auth_level_step)
        elif data[0:3] == b'\x06\x00\x00':
            device_key = data[3:19]
            auth_state = "GOT_DEVICE_KEY"
            print("Device key saved:", device_key.hex())
            auth_state = "AUTH_SUCCESS"
            print("Authentication successful")

def callback2(sender: str, data: bytearray) -> None:
    print(f"callback2 invoked with sender: {sender}, data: {data.hex()}")
    if sender == CHARACTERISTIC_AUTH_AND_SERVICE and (data[1] & 1) == 1:
        print("Callback2 triggered with valid data.")
        # Here you can implement the logic to move between authentication states

async def start_notify_auth_and_service():
    print("Connecting to start AUTH_AND_SERVICE notifications...")
    async with BleakClient(DEVICE_MAC_ADDRESS) as client:
        await client.start_notify(CHARACTERISTIC_AUTH_AND_SERVICE, callback2)
        some_data = bytearray([0x01, 0x02, 0x03])  # Replace this with actual data as needed
        await asyncio.sleep(1)  # Allow time for notifications to stabilize
        await client.write_gatt_char(CHARACTERISTIC_AUTH_AND_SERVICE, some_data)
        print("Started notifications for AUTH_AND_SERVICE characteristic")

async def main():
    print(f"Connecting to device {DEVICE_MAC_ADDRESS}...")
    async with BleakClient(DEVICE_MAC_ADDRESS) as client:
        await client.start_notify(CHARACTERISTIC_AUTH, callback)
        print("Started notifications for AUTH characteristic")

        while auth_state != "AUTH_SUCCESS":
            await asyncio.sleep(1)
            if auth_state == "GOT_NONCE":
                print("Press the button on the device to confirm...")

if __name__ == "__main__":
    asyncio.run(main())

Can you please share your code? I am new to bluetooth communication to be true, but want to try it.

I think you have a mistake in sender == CHARACTERISTIC_AUTH you should use something like str(sender).split(' ')[0] == CHARACTERISTIC_AUTH

I updated the script but still couldn’t get it to work

import asyncio
from bleak import BleakClient
from bleak.exc import BleakError, BleakDBusError

CHARACTERISTIC_AUTH = "fdce2347-1013-4120-b919-1dbb32a2d132"
CHARACTERISTIC_AUTH_AND_SERVICE = "fdce2348-1013-4120-b919-1dbb32a2d132"

DEVICE_MAC_ADDRESS = "E1:A6:44:55:39:CA"

# Global variables to store the data and state
nonce = None
auth_level_step = None
device_key = None
auth_state = "INIT"

def callback(sender: str, data: bytearray) -> None:
    global nonce, auth_level_step, device_key, auth_state
    sender_str = str(sender).split(' ')[0]
    print(f"callback invoked with sender: {sender_str}, data: {data.hex()}")

    if sender_str == CHARACTERISTIC_AUTH:
        if len(data) == 20 and data[0] == 1:
            nonce = data
            auth_state = "GOT_NONCE"
            print("Nonce saved:", nonce.hex())
            asyncio.create_task(start_notify_auth_and_service())
        elif data[0] == 4 and data[2] == 2:
            auth_level_step = data[1]
            auth_state = "CONFIRM_WAITING"
            print("Auth level/step saved:", auth_level_step)
        elif data[0:3] == b'\x06\x00\x00':
            device_key = data[3:19]
            auth_state = "GOT_DEVICE_KEY"
            print("Device key saved:", device_key.hex())
            auth_state = "AUTH_SUCCESS"
            print("Authentication successful")

def callback2(sender: str, data: bytearray) -> None:
    sender_str = str(sender).split(' ')[0]
    print(f"callback2 invoked with sender: {sender_str}, data: {data.hex()}")
    if sender_str == CHARACTERISTIC_AUTH_AND_SERVICE and (data[1] & 1) == 1:
        print("Callback2 triggered with valid data.")
        # Implement the logic to move between authentication states

async def start_notify_auth_and_service():
    retry_attempts = 3
    for attempt in range(retry_attempts):
        try:
            print("Connecting to start AUTH_AND_SERVICE notifications...")
            async with BleakClient(DEVICE_MAC_ADDRESS) as client:
                await client.start_notify(CHARACTERISTIC_AUTH_AND_SERVICE, callback2)
                some_data = bytearray([0x01, 0x02, 0x03])  # Replace this with actual data as needed
                await asyncio.sleep(1)  # Allow time for notifications to stabilize
                await client.write_gatt_char(CHARACTERISTIC_AUTH_AND_SERVICE, some_data)
                print("Started notifications for AUTH_AND_SERVICE characteristic")
                break
        except BleakDBusError as e:
            print(f"Failed to start notifications (attempt {attempt + 1}): {e}")
            await asyncio.sleep(2)
        except BleakError as e:
            print(f"BLE error (attempt {attempt + 1}): {e}")
            await asyncio.sleep(2)

async def main():
    print(f"Connecting to device {DEVICE_MAC_ADDRESS}...")
    try:
        async with BleakClient(DEVICE_MAC_ADDRESS) as client:
            await client.start_notify(CHARACTERISTIC_AUTH, callback)
            print("Started notifications for AUTH characteristic")

            while auth_state != "AUTH_SUCCESS":
                await asyncio.sleep(1)
                if auth_state == "GOT_NONCE":
                    print("Press the button on the device to confirm...")
    except BleakDBusError as e:
        print(f"Failed to connect to device: {e}")
    except BleakError as e:
        print(f"BLE error: {e}")

if __name__ == "__main__":
    asyncio.run(main())

Any news on that? have two h700 as well