Skip to content

Commit 2abd0b9

Browse files
committed
Updates
1 parent 050940e commit 2abd0b9

File tree

11 files changed

+371
-91
lines changed

11 files changed

+371
-91
lines changed

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ The integration works locally, but connection to Tuya BLE device requires device
2323
* Fingerbots (category_id 'szjqr')
2424
+ Fingerbot (product_ids 'ltak7e1p', 'y6kttvd6', 'yrnk7mnn', 'nvr2rocq', 'bnt7wajf', 'rvdceqjh', '5xhbk964'), original device, first in category, powered by CR2 battery.
2525
+ Adaprox Fingerbot (product_id 'y6kttvd6'), built-in battery with USB type C charging.
26-
+ Fingerbot Plus (product_ids 'blliqpsj', 'yiihr7zh'), almost same as original, has sensor button for manual control.
26+
+ Fingerbot Plus (product_ids 'blliqpsj', 'yiihr7zh', 'neq16kgd'), almost same as original, has sensor button for manual control.
2727
+ CubeTouch 1s (product_id '3yqdo5yt'), built-in battery with USB type C charging.
2828
+ CubeTouch II (product_id 'xhf790if'), built-in battery with USB type C charging.
2929

@@ -41,6 +41,9 @@ The integration works locally, but connection to Tuya BLE device requires device
4141
* Climate (category_id 'wk')
4242
+ Thermostatic Radiator Valve (product_ids 'drlajpqc', 'nhj2j7su'), first attempt to support for now.
4343

44+
* Smart water bottle (category_id 'znhsb')
45+
+ Smart water bottle (product_id 'cdlandip')
46+
4447
## Support project
4548

4649
I am working on this integration in Ukraine. Our country was subjected to brutal aggression by Russia. The war still continues. The capital of Ukraine - Kyiv, where I live, and many other cities and villages are constantly under threat of rocket attacks. Our air defense forces are doing wonders, but they also need support. So if you want to help the development of this integration, donate some money and I will spend it to support our air defense.

custom_components/tuya_ble/button.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,12 @@ class TuyaBLECategoryButtonMapping:
6969
],
7070
),
7171
**dict.fromkeys(
72-
["blliqpsj", "ndvkgsrm", "yiihr7zh"], # Fingerbot Plus
72+
[
73+
"blliqpsj",
74+
"ndvkgsrm",
75+
"yiihr7zh",
76+
"neq16kgd"
77+
], # Fingerbot Plus
7378
[
7479
TuyaBLEFingerbotModeMapping(dp_id=2),
7580
],
@@ -90,6 +95,19 @@ class TuyaBLECategoryButtonMapping:
9095
),
9196
},
9297
),
98+
"znhsb": TuyaBLECategoryButtonMapping(
99+
products={
100+
"cdlandip": # Smart water bottle
101+
[
102+
TuyaBLEButtonMapping(
103+
dp_id=109,
104+
description=ButtonEntityDescription(
105+
key="bright_lid_screen",
106+
),
107+
),
108+
],
109+
},
110+
),
93111
}
94112

95113

custom_components/tuya_ble/const.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,5 +36,6 @@
3636

3737
FINGERBOT_MODE_PUSH: Final = "push"
3838
FINGERBOT_MODE_SWITCH: Final = "switch"
39+
FINGERBOT_MODE_PROGRAM: Final = "program"
3940
FINGERBOT_BUTTON_EVENT: Final = "fingerbot_button_pressed"
4041

custom_components/tuya_ble/devices.py

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ def _async_handle_update(self, updates: list[TuyaBLEDataPoint]) -> None:
127127
self._async_handle_connect()
128128
self.async_set_updated_data(None)
129129
info = get_device_product_info(self._device)
130-
if info.fingerbot and info.fingerbot.manual_control != 0:
130+
if info and info.fingerbot and info.fingerbot.manual_control != 0:
131131
for update in updates:
132132
if update.id == info.fingerbot.switch and update.changed_by_device:
133133
self.hass.bus.fire(
@@ -212,7 +212,12 @@ class TuyaBLECategoryInfo:
212212
),
213213
),
214214
**dict.fromkeys(
215-
["blliqpsj", "ndvkgsrm", "yiihr7zh"], # device product_ids
215+
[
216+
"blliqpsj",
217+
"ndvkgsrm",
218+
"yiihr7zh",
219+
"neq16kgd"
220+
], # device product_ids
216221
TuyaBLEProductInfo(
217222
name="Fingerbot Plus",
218223
fingerbot=TuyaBLEFingerbotInfo(
@@ -253,11 +258,11 @@ class TuyaBLECategoryInfo:
253258
"wk": TuyaBLECategoryInfo(
254259
products={
255260
**dict.fromkeys(
256-
[
257-
"drlajpqc",
258-
"nhj2j7su",
259-
], # device product_id
260-
TuyaBLEProductInfo(
261+
[
262+
"drlajpqc",
263+
"nhj2j7su",
264+
], # device product_id
265+
TuyaBLEProductInfo(
261266
name="Thermostatic Radiator Valve",
262267
),
263268
),
@@ -270,6 +275,14 @@ class TuyaBLECategoryInfo:
270275
),
271276
},
272277
),
278+
"znhsb": TuyaBLECategoryInfo(
279+
products={
280+
"cdlandip": # device product_id
281+
TuyaBLEProductInfo(
282+
name="Smart water bottle",
283+
),
284+
},
285+
),
273286
}
274287

275288

custom_components/tuya_ble/number.py

Lines changed: 72 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,14 @@
1010
NumberEntityDescription,
1111
NumberEntity,
1212
)
13-
from homeassistant.components.number.const import NumberMode
13+
from homeassistant.components.number.const import NumberDeviceClass, NumberMode
1414
from homeassistant.config_entries import ConfigEntry
1515
from homeassistant.const import (
1616
CONCENTRATION_PARTS_PER_MILLION,
1717
PERCENTAGE,
1818
TIME_MINUTES,
1919
TIME_SECONDS,
20+
VOLUME_MILLILITERS,
2021
UnitOfTemperature,
2122
)
2223
from homeassistant.core import HomeAssistant, callback
@@ -36,7 +37,9 @@
3637
)
3738

3839

39-
TuyaBLENumberIsAvailable = Callable[["TuyaBLENumber", TuyaBLEProductInfo], bool] | None
40+
TuyaBLENumberIsAvailable = (
41+
Callable[["TuyaBLENumber", TuyaBLEProductInfo], bool] | None
42+
)
4043

4144

4245
TuyaBLENumberSetter = (
@@ -57,6 +60,33 @@ class TuyaBLENumberMapping:
5760
mode: NumberMode = NumberMode.BOX
5861

5962

63+
def is_fingerbot_not_in_program_mode(self: TuyaBLENumber, product: TuyaBLEProductInfo) -> bool:
64+
result: bool = True
65+
if product.fingerbot:
66+
datapoint = self._device.datapoints[product.fingerbot.mode]
67+
if datapoint:
68+
result = datapoint.value != 2
69+
return result
70+
71+
72+
def is_fingerbot_in_program_mode(self: TuyaBLENumber, product: TuyaBLEProductInfo) -> bool:
73+
result: bool = True
74+
if product.fingerbot:
75+
datapoint = self._device.datapoints[product.fingerbot.mode]
76+
if datapoint:
77+
result = datapoint.value == 2
78+
return result
79+
80+
81+
def is_fingerbot_in_push_mode(self: TuyaBLENumber, product: TuyaBLEProductInfo) -> bool:
82+
result: bool = True
83+
if product.fingerbot:
84+
datapoint = self._device.datapoints[product.fingerbot.mode]
85+
if datapoint:
86+
result = datapoint.value == 0
87+
return result
88+
89+
6090
@dataclass
6191
class TuyaBLEDownPositionDescription(NumberEntityDescription):
6292
key: str = "down_position"
@@ -79,15 +109,6 @@ class TuyaBLEUpPositionDescription(NumberEntityDescription):
79109
entity_category: EntityCategory = EntityCategory.CONFIG
80110

81111

82-
def is_fingerbot_in_push_mode(self: TuyaBLENumber, product: TuyaBLEProductInfo) -> bool:
83-
result: bool = True
84-
if product.fingerbot:
85-
datapoint = self._device.datapoints[product.fingerbot.mode]
86-
if datapoint:
87-
result = datapoint.value == 0
88-
return result
89-
90-
91112
@dataclass
92113
class TuyaBLEHoldTimeDescription(NumberEntityDescription):
93114
key: str = "hold_time"
@@ -183,16 +204,23 @@ class TuyaBLECategoryNumberMapping:
183204
],
184205
),
185206
**dict.fromkeys(
186-
["blliqpsj", "ndvkgsrm", "yiihr7zh"], # Fingerbot Plus
207+
[
208+
"blliqpsj",
209+
"ndvkgsrm",
210+
"yiihr7zh",
211+
"neq16kgd"
212+
], # Fingerbot Plus
187213
[
188214
TuyaBLENumberMapping(
189215
dp_id=9,
190216
description=TuyaBLEDownPositionDescription(),
217+
is_available=is_fingerbot_not_in_program_mode,
191218
),
192219
TuyaBLEHoldTimeMapping(dp_id=10),
193220
TuyaBLENumberMapping(
194221
dp_id=15,
195222
description=TuyaBLEUpPositionDescription(),
223+
is_available=is_fingerbot_not_in_program_mode,
196224
),
197225
],
198226
),
@@ -210,6 +238,7 @@ class TuyaBLECategoryNumberMapping:
210238
TuyaBLENumberMapping(
211239
dp_id=9,
212240
description=TuyaBLEDownPositionDescription(),
241+
is_available=is_fingerbot_not_in_program_mode,
213242
),
214243
TuyaBLENumberMapping(
215244
dp_id=10,
@@ -222,6 +251,7 @@ class TuyaBLECategoryNumberMapping:
222251
TuyaBLENumberMapping(
223252
dp_id=15,
224253
description=TuyaBLEUpPositionDescription(),
254+
is_available=is_fingerbot_not_in_program_mode,
225255
),
226256
],
227257
),
@@ -235,17 +265,17 @@ class TuyaBLECategoryNumberMapping:
235265
"nhj2j7su",
236266
], # Thermostatic Radiator Valve
237267
[
238-
TuyaBLENumberMapping(
239-
dp_id=27,
240-
description=NumberEntityDescription(
241-
key="temperature_calibration",
242-
icon="mdi:thermometer-lines",
243-
native_max_value=6,
244-
native_min_value=-6,
245-
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
246-
native_step=1,
247-
entity_category=EntityCategory.CONFIG,
248-
entity_registry_enabled_default=True,
268+
TuyaBLENumberMapping(
269+
dp_id=27,
270+
description=NumberEntityDescription(
271+
key="temperature_calibration",
272+
icon="mdi:thermometer-lines",
273+
native_max_value=6,
274+
native_min_value=-6,
275+
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
276+
native_step=1,
277+
entity_category=EntityCategory.CONFIG,
278+
entity_registry_enabled_default=True,
249279
),
250280
),
251281
],
@@ -270,6 +300,25 @@ class TuyaBLECategoryNumberMapping:
270300
],
271301
},
272302
),
303+
"znhsb": TuyaBLECategoryNumberMapping(
304+
products={
305+
"cdlandip": # Smart water bottle
306+
[
307+
TuyaBLENumberMapping(
308+
dp_id=103,
309+
description=NumberEntityDescription(
310+
key="recommended_water_intake",
311+
device_class= NumberDeviceClass.WATER,
312+
native_max_value=5000,
313+
native_min_value=0,
314+
native_unit_of_measurement=VOLUME_MILLILITERS,
315+
native_step=1,
316+
entity_category=EntityCategory.CONFIG,
317+
),
318+
),
319+
],
320+
},
321+
),
273322
}
274323

275324

custom_components/tuya_ble/select.py

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
from .const import (
2020
DOMAIN,
21+
FINGERBOT_MODE_PROGRAM,
2122
FINGERBOT_MODE_PUSH,
2223
FINGERBOT_MODE_SWITCH,
2324
)
@@ -48,7 +49,12 @@ class TuyaBLEFingerbotModeMapping(TuyaBLESelectMapping):
4849
default_factory=lambda: SelectEntityDescription(
4950
key="fingerbot_mode",
5051
entity_category=EntityCategory.CONFIG,
51-
options=[FINGERBOT_MODE_PUSH, FINGERBOT_MODE_SWITCH],
52+
options=
53+
[
54+
FINGERBOT_MODE_PUSH,
55+
FINGERBOT_MODE_SWITCH,
56+
FINGERBOT_MODE_PROGRAM,
57+
],
5258
)
5359
)
5460

@@ -105,7 +111,12 @@ class TuyaBLECategorySelectMapping:
105111
],
106112
),
107113
**dict.fromkeys(
108-
["blliqpsj", "ndvkgsrm", "yiihr7zh"], # Fingerbot Plus
114+
[
115+
"blliqpsj",
116+
"ndvkgsrm",
117+
"yiihr7zh",
118+
"neq16kgd"
119+
], # Fingerbot Plus
109120
[
110121
TuyaBLEFingerbotModeMapping(dp_id=8),
111122
],
@@ -137,6 +148,33 @@ class TuyaBLECategorySelectMapping:
137148
],
138149
},
139150
),
151+
"znhsb": TuyaBLECategorySelectMapping(
152+
products={
153+
"cdlandip": # Smart water bottle
154+
[
155+
TuyaBLESelectMapping(
156+
dp_id=106,
157+
description=TemperatureUnitDescription(
158+
options=[
159+
UnitOfTemperature.CELSIUS,
160+
UnitOfTemperature.FAHRENHEIT,
161+
],
162+
)
163+
),
164+
TuyaBLESelectMapping(
165+
dp_id=107,
166+
description=SelectEntityDescription(
167+
key="reminder_mode",
168+
options=[
169+
"interval_reminder",
170+
"schedule_reminder",
171+
],
172+
entity_category=EntityCategory.CONFIG,
173+
),
174+
),
175+
],
176+
},
177+
),
140178
}
141179

142180

0 commit comments

Comments
 (0)