From cb119294e393c70ff0fc0da5e16435de84215168 Mon Sep 17 00:00:00 2001 From: wenil Date: Wed, 22 Jul 2026 01:56:07 +0300 Subject: [PATCH] FD50 GATT fallback patch (vs ShonP40/Tuya-BLE master dabbf9e) --- fd50-gatt-fallback.patch | 89 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 fd50-gatt-fallback.patch diff --git a/fd50-gatt-fallback.patch b/fd50-gatt-fallback.patch new file mode 100644 index 0000000..07976b1 --- /dev/null +++ b/fd50-gatt-fallback.patch @@ -0,0 +1,89 @@ +diff --git a/custom_components/tuya_local_ble/tuya_ble/const.py b/custom_components/tuya_local_ble/tuya_ble/const.py +index b1514a9..c73f268 100644 +--- a/custom_components/tuya_local_ble/tuya_ble/const.py ++++ b/custom_components/tuya_local_ble/tuya_ble/const.py +@@ -9,6 +9,12 @@ DEFAULT_ATTEMPTS = 0xFFFF + CHARACTERISTIC_NOTIFY = "00002b10-0000-1000-8000-00805f9b34fb" + CHARACTERISTIC_WRITE = "00002b11-0000-1000-8000-00805f9b34fb" + ++# Newer TuyaOS firmware (e.g. jtmspro hc7n0urm fw 6.0) drops the legacy ++# 0xA201 service and exposes the data channel via these characteristics ++# of the 0xFD50 service instead. ++CHARACTERISTIC_NOTIFY_FD50 = "00000002-0000-1001-8001-00805f9b07d0" ++CHARACTERISTIC_WRITE_FD50 = "00000001-0000-1001-8001-00805f9b07d0" ++ + SERVICE_UUID = "0000a201-0000-1000-8000-00805f9b34fb" + + MANUFACTURER_DATA_ID = 0x07D0 +diff --git a/custom_components/tuya_local_ble/tuya_ble/tuya_ble.py b/custom_components/tuya_local_ble/tuya_ble/tuya_ble.py +index 274cbf6..73049eb 100644 +--- a/custom_components/tuya_local_ble/tuya_ble/tuya_ble.py ++++ b/custom_components/tuya_local_ble/tuya_ble/tuya_ble.py +@@ -24,7 +24,9 @@ from Crypto.Cipher import AES + + from .const import ( + CHARACTERISTIC_NOTIFY, ++ CHARACTERISTIC_NOTIFY_FD50, + CHARACTERISTIC_WRITE, ++ CHARACTERISTIC_WRITE_FD50, + GATT_MTU, + MANUFACTURER_DATA_ID, + RESPONSE_WAIT_TIMEOUT, +@@ -231,6 +233,9 @@ class TuyaBLEDevice: + self._current_seq_num = 1 + self._seq_num_lock = asyncio.Lock() + ++ self._characteristic_notify = CHARACTERISTIC_NOTIFY ++ self._characteristic_write = CHARACTERISTIC_WRITE ++ + self._is_bound = False + self._flags = 0 + self._protocol_version = 2 +@@ -540,11 +545,25 @@ class TuyaBLEDevice: + self._expected_disconnect = True + self._client = None + if client and client.is_connected: +- await client.stop_notify(CHARACTERISTIC_NOTIFY) ++ await client.stop_notify(self._characteristic_notify) + await client.disconnect() + async with self._seq_num_lock: + self._current_seq_num = 1 + ++ def _select_characteristics(self, client: BleakClientWithServiceCache) -> None: ++ """Select the GATT channel actually exposed by the device.""" ++ if client.services.get_characteristic(CHARACTERISTIC_NOTIFY): ++ self._characteristic_notify = CHARACTERISTIC_NOTIFY ++ self._characteristic_write = CHARACTERISTIC_WRITE ++ elif client.services.get_characteristic(CHARACTERISTIC_NOTIFY_FD50): ++ _LOGGER.debug( ++ "%s: legacy characteristics not present," ++ " using FD50 GATT channel", ++ self.address, ++ ) ++ self._characteristic_notify = CHARACTERISTIC_NOTIFY_FD50 ++ self._characteristic_write = CHARACTERISTIC_WRITE_FD50 ++ + async def _ensure_connected(self) -> None: + """Ensure connection to device is established.""" + global global_connect_lock +@@ -609,9 +628,10 @@ class TuyaBLEDevice: + _LOGGER.debug("%s: Connected; RSSI: %s", + self.address, self.rssi) + self._client = client ++ self._select_characteristics(client) + try: + await self._client.start_notify( +- CHARACTERISTIC_NOTIFY, ++ self._characteristic_notify, + self._notification_handler, + bluez={"use_start_notify": True}, + ) +@@ -968,7 +988,7 @@ class TuyaBLEDevice: + try: + # _LOGGER.debug("%s: Sending packet: %s", self.address, packet.hex()) + await self._client.write_gatt_char( +- CHARACTERISTIC_WRITE, ++ self._characteristic_write, + packet, + False, + )