Skip to content

Commit c11a1c5

Browse files
committed
Update to version 0.1.7
### Added - Added new product_ids. - Added full support of BLE TRV provided by @forabi - Added support of programming mode for Fingerbot Plus, thanks @redphx for information. ### Changed - Improved connection stability.
1 parent 2abd0b9 commit c11a1c5

File tree

14 files changed

+562
-142
lines changed

14 files changed

+562
-142
lines changed

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,15 @@ and this project adheres to [Semantic Versioning].
6262

6363
- Updated sources to conform Python 3.11
6464

65+
## [0.1.7] - 2023-06-01
66+
67+
### Added
68+
69+
- Added new product_ids.
70+
- Added full support of BLE TRV provided by @forabi
71+
- Added support of programming mode for Fingerbot Plus, thanks @redphx for information.
72+
73+
### Changed
74+
75+
- Improved connection stability.
76+

README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,12 @@ 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', 'neq16kgd'), almost same as original, has sensor button for manual control.
26+
+ Fingerbot Plus (product_ids 'blliqpsj', 'ndvkgsrm', '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

30-
All features available in Home Assistant, except programming (series of actions) - it's not documented and looks useless because it could be implemented by Home Assistant scripts or automations.
30+
All features available in Home Assistant, programming (series of actions) is implemented for Fingerbot Plus.
31+
For programming exposed entities 'Program' (switch), 'Repeat forever', 'Repeats count', 'Idle position' and 'Program' (text). Format of program text is: 'position\[/time\];...' where position is in percents, optional time is in seconds (zero if missing).
3132

3233
* Temperature and humidity sensors (category_id 'wsdcg')
3334
+ Soil moisture sensor (product_id 'ojzlzzsw').
@@ -39,7 +40,7 @@ The integration works locally, but connection to Tuya BLE device requires device
3940
+ Smart Lock (product_id 'ludzroix'), first attempt to support for now.
4041

4142
* Climate (category_id 'wk')
42-
+ Thermostatic Radiator Valve (product_ids 'drlajpqc', 'nhj2j7su'), first attempt to support for now.
43+
+ Thermostatic Radiator Valve (product_ids 'drlajpqc', 'nhj2j7su').
4344

4445
* Smart water bottle (category_id 'znhsb')
4546
+ Smart water bottle (product_id 'cdlandip')

custom_components/tuya_ble/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
Platform.BINARY_SENSOR,
2727
Platform.SELECT,
2828
Platform.SWITCH,
29+
Platform.TEXT,
2930
]
3031

3132
_LOGGER = logging.getLogger(__name__)

custom_components/tuya_ble/binary_sensor.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ class TuyaBLEBinarySensorMapping:
4040
force_add: bool = True
4141
dp_type: TuyaBLEDataPointType | None = None
4242
getter: Callable[[TuyaBLEBinarySensor], None] | None = None
43-
coefficient: float = 1.0
44-
icons: list[str] | None = None
43+
#coefficient: float = 1.0
44+
#icons: list[str] | None = None
4545
is_available: TuyaBLEBinarySensorIsAvailable = None
4646

4747

@@ -58,11 +58,10 @@ class TuyaBLECategoryBinarySensorMapping:
5858
TuyaBLEBinarySensorMapping(
5959
dp_id=105,
6060
description=BinarySensorEntityDescription(
61-
key="low_battery",
62-
icon="mdi:battery-alert",
61+
key="battery",
62+
#icon="mdi:battery-alert",
6363
device_class=BinarySensorDeviceClass.BATTERY,
6464
entity_category=EntityCategory.DIAGNOSTIC,
65-
entity_registry_enabled_default=True,
6665
),
6766
),
6867
],
@@ -107,6 +106,8 @@ def _handle_coordinator_update(self) -> None:
107106
else:
108107
datapoint = self._device.datapoints[self._mapping.dp_id]
109108
if datapoint:
109+
self._attr_is_on = bool(datapoint.value)
110+
'''
110111
if datapoint.type == TuyaBLEDataPointType.DT_ENUM:
111112
if self.entity_description.options is not None:
112113
if datapoint.value >= 0 and datapoint.value < len(
@@ -128,6 +129,7 @@ def _handle_coordinator_update(self) -> None:
128129
)
129130
else:
130131
self._attr_native_value = datapoint.value
132+
'''
131133
self.async_write_ha_state()
132134

133135
@property

custom_components/tuya_ble/devices.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ class TuyaBLEFingerbotInfo:
4646
hold_time: int
4747
reverse_positions: int
4848
manual_control: int = 0
49+
program: int = 0
4950

5051

5152
@dataclass
@@ -228,6 +229,7 @@ class TuyaBLECategoryInfo:
228229
hold_time=10,
229230
reverse_positions=11,
230231
manual_control=17,
232+
program=121,
231233
),
232234
),
233235
),
@@ -250,6 +252,7 @@ class TuyaBLECategoryInfo:
250252
down_position=9,
251253
hold_time=10,
252254
reverse_positions=11,
255+
program=121,
253256
),
254257
),
255258
),

custom_components/tuya_ble/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,5 @@
1313
"documentation": "https://www.home-assistant.io/integrations/tuya_ble",
1414
"requirements": ["tuya-iot-py-sdk==0.6.6", "pycountry==22.3.5"],
1515
"iot_class": "local_push",
16-
"version": "0.1.6"
16+
"version": "0.1.7"
1717
}

custom_components/tuya_ble/number.py

Lines changed: 123 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343

4444

4545
TuyaBLENumberSetter = (
46-
Callable[["TuyaBLENumber", TuyaBLEProductInfo, float], bool] | None
46+
Callable[["TuyaBLENumber", TuyaBLEProductInfo, float], None] | None
4747
)
4848

4949

@@ -60,25 +60,34 @@ class TuyaBLENumberMapping:
6060
mode: NumberMode = NumberMode.BOX
6161

6262

63-
def is_fingerbot_not_in_program_mode(self: TuyaBLENumber, product: TuyaBLEProductInfo) -> bool:
63+
def is_fingerbot_in_program_mode(
64+
self: TuyaBLENumber,
65+
product: TuyaBLEProductInfo,
66+
) -> bool:
6467
result: bool = True
6568
if product.fingerbot:
6669
datapoint = self._device.datapoints[product.fingerbot.mode]
6770
if datapoint:
68-
result = datapoint.value != 2
71+
result = datapoint.value == 2
6972
return result
7073

7174

72-
def is_fingerbot_in_program_mode(self: TuyaBLENumber, product: TuyaBLEProductInfo) -> bool:
75+
def is_fingerbot_not_in_program_mode(
76+
self: TuyaBLENumber,
77+
product: TuyaBLEProductInfo,
78+
) -> bool:
7379
result: bool = True
7480
if product.fingerbot:
7581
datapoint = self._device.datapoints[product.fingerbot.mode]
7682
if datapoint:
77-
result = datapoint.value == 2
83+
result = datapoint.value != 2
7884
return result
7985

8086

81-
def is_fingerbot_in_push_mode(self: TuyaBLENumber, product: TuyaBLEProductInfo) -> bool:
87+
def is_fingerbot_in_push_mode(
88+
self: TuyaBLENumber,
89+
product: TuyaBLEProductInfo,
90+
) -> bool:
8291
result: bool = True
8392
if product.fingerbot:
8493
datapoint = self._device.datapoints[product.fingerbot.mode]
@@ -87,6 +96,79 @@ def is_fingerbot_in_push_mode(self: TuyaBLENumber, product: TuyaBLEProductInfo)
8796
return result
8897

8998

99+
def is_fingerbot_repeat_count_available(
100+
self: TuyaBLENumber,
101+
product: TuyaBLEProductInfo,
102+
) -> bool:
103+
result: bool = True
104+
if product.fingerbot and product.fingerbot.program:
105+
datapoint = self._device.datapoints[product.fingerbot.mode]
106+
if datapoint:
107+
result = datapoint.value == 2
108+
if result:
109+
datapoint = self._device.datapoints[product.fingerbot.program]
110+
if datapoint and type(datapoint.value) is bytes:
111+
repeat_count = int.from_bytes(datapoint.value[0:2], "big")
112+
result = repeat_count != 0xFFFF
113+
114+
return result
115+
116+
117+
def get_fingerbot_program_repeat_count(
118+
self: TuyaBLENumber,
119+
product: TuyaBLEProductInfo,
120+
) -> float | None:
121+
result: float | None = None
122+
if product.fingerbot and product.fingerbot.program:
123+
datapoint = self._device.datapoints[product.fingerbot.program]
124+
if datapoint and type(datapoint.value) is bytes:
125+
repeat_count = int.from_bytes(datapoint.value[0:2], "big")
126+
result = repeat_count * 1.0
127+
128+
return result
129+
130+
131+
def set_fingerbot_program_repeat_count(
132+
self: TuyaBLENumber,
133+
product: TuyaBLEProductInfo,
134+
value: float,
135+
) -> None:
136+
if product.fingerbot and product.fingerbot.program:
137+
datapoint = self._device.datapoints[product.fingerbot.program]
138+
if datapoint and type(datapoint.value) is bytes:
139+
new_value = (
140+
int.to_bytes(int(value), 2, "big") +
141+
datapoint.value[2:]
142+
)
143+
self._hass.create_task(datapoint.set_value(new_value))
144+
145+
146+
def get_fingerbot_program_position(
147+
self: TuyaBLENumber,
148+
product: TuyaBLEProductInfo,
149+
) -> float | None:
150+
result: float | None = None
151+
if product.fingerbot and product.fingerbot.program:
152+
datapoint = self._device.datapoints[product.fingerbot.program]
153+
if datapoint and type(datapoint.value) is bytes:
154+
result = datapoint.value[2] * 1.0
155+
156+
return result
157+
158+
159+
def set_fingerbot_program_position(
160+
self: TuyaBLENumber,
161+
product: TuyaBLEProductInfo,
162+
value: float,
163+
) -> None:
164+
if product.fingerbot and product.fingerbot.program:
165+
datapoint = self._device.datapoints[product.fingerbot.program]
166+
if datapoint and type(datapoint.value) is bytes:
167+
new_value = bytearray(datapoint.value)
168+
new_value[2] = int(value)
169+
self._hass.create_task(datapoint.set_value(new_value))
170+
171+
90172
@dataclass
91173
class TuyaBLEDownPositionDescription(NumberEntityDescription):
92174
key: str = "down_position"
@@ -207,7 +289,7 @@ class TuyaBLECategoryNumberMapping:
207289
[
208290
"blliqpsj",
209291
"ndvkgsrm",
210-
"yiihr7zh",
292+
"yiihr7zh",
211293
"neq16kgd"
212294
], # Fingerbot Plus
213295
[
@@ -222,6 +304,35 @@ class TuyaBLECategoryNumberMapping:
222304
description=TuyaBLEUpPositionDescription(),
223305
is_available=is_fingerbot_not_in_program_mode,
224306
),
307+
TuyaBLENumberMapping(
308+
dp_id=121,
309+
description=NumberEntityDescription(
310+
key="program_repeats_count",
311+
icon="mdi:repeat",
312+
native_max_value=0xFFFE,
313+
native_min_value=1,
314+
native_step=1,
315+
entity_category=EntityCategory.CONFIG,
316+
),
317+
is_available=is_fingerbot_repeat_count_available,
318+
getter=get_fingerbot_program_repeat_count,
319+
setter=set_fingerbot_program_repeat_count,
320+
),
321+
TuyaBLENumberMapping(
322+
dp_id=121,
323+
description=NumberEntityDescription(
324+
key="program_idle_position",
325+
icon="mdi:repeat",
326+
native_max_value=100,
327+
native_min_value=0,
328+
native_step=1,
329+
native_unit_of_measurement=PERCENTAGE,
330+
entity_category=EntityCategory.CONFIG,
331+
),
332+
is_available=is_fingerbot_in_program_mode,
333+
getter=get_fingerbot_program_position,
334+
setter=set_fingerbot_program_position,
335+
),
225336
],
226337
),
227338
**dict.fromkeys(
@@ -261,8 +372,8 @@ class TuyaBLECategoryNumberMapping:
261372
products={
262373
**dict.fromkeys(
263374
[
264-
"drlajpqc",
265-
"nhj2j7su",
375+
"drlajpqc",
376+
"nhj2j7su",
266377
], # Thermostatic Radiator Valve
267378
[
268379
TuyaBLENumberMapping(
@@ -275,7 +386,6 @@ class TuyaBLECategoryNumberMapping:
275386
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
276387
native_step=1,
277388
entity_category=EntityCategory.CONFIG,
278-
entity_registry_enabled_default=True,
279389
),
280390
),
281391
],
@@ -308,7 +418,7 @@ class TuyaBLECategoryNumberMapping:
308418
dp_id=103,
309419
description=NumberEntityDescription(
310420
key="recommended_water_intake",
311-
device_class= NumberDeviceClass.WATER,
421+
device_class=NumberDeviceClass.WATER,
312422
native_max_value=5000,
313423
native_min_value=0,
314424
native_unit_of_measurement=VOLUME_MILLILITERS,
@@ -366,8 +476,8 @@ def native_value(self) -> float | None:
366476
def set_native_value(self, value: float) -> None:
367477
"""Set new value."""
368478
if self._mapping.setter:
369-
if self._mapping.setter(self, self._product, value):
370-
return
479+
self._mapping.setter(self, self._product, value)
480+
return
371481
int_value = int(value * self._mapping.coefficient)
372482
datapoint = self._device.datapoints.get_or_create(
373483
self._mapping.dp_id,
@@ -386,22 +496,6 @@ def available(self) -> bool:
386496
return result
387497

388498

389-
class TuyaBLEFingerbotPosition(TuyaBLENumber, RestoreEntity):
390-
def __init__(
391-
self,
392-
hass: HomeAssistant,
393-
coordinator: DataUpdateCoordinator,
394-
device: TuyaBLEDevice,
395-
mapping: TuyaBLENumberMapping,
396-
) -> None:
397-
super().__init__(hass, coordinator, device, mapping)
398-
399-
async def async_internal_added_to_hass(self) -> None:
400-
"""Register this entity as a restorable entity."""
401-
last_state = await self.async_get_last_state()
402-
self._attr_native_value = last_state.state
403-
404-
405499
async def async_setup_entry(
406500
hass: HomeAssistant,
407501
entry: ConfigEntry,

custom_components/tuya_ble/select.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,33 @@ class TuyaBLECategorySelectMapping:
175175
],
176176
},
177177
),
178+
"znhsb": TuyaBLECategorySelectMapping(
179+
products={
180+
"cdlandip": # Smart water bottle
181+
[
182+
TuyaBLESelectMapping(
183+
dp_id=106,
184+
description=TemperatureUnitDescription(
185+
options=[
186+
UnitOfTemperature.CELSIUS,
187+
UnitOfTemperature.FAHRENHEIT,
188+
],
189+
)
190+
),
191+
TuyaBLESelectMapping(
192+
dp_id=107,
193+
description=SelectEntityDescription(
194+
key="reminder_mode",
195+
options=[
196+
"interval_reminder",
197+
"alarm_reminder",
198+
],
199+
entity_category=EntityCategory.CONFIG,
200+
),
201+
),
202+
],
203+
},
204+
),
178205
}
179206

180207

0 commit comments

Comments
 (0)