Last active
March 12, 2026 16:47
-
-
Save crstian19/fceda5c8bd032b2971c82ffc7127a808 to your computer and use it in GitHub Desktop.
ZHA quirk for Tuya TS130F curtain module - manufacturer _TZ3000_bs93npae
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| """ZHA quirk for Tuya TS130F curtain module - manufacturer _TZ3000_bs93npae. | |
| Device: QS-Zigbee-C01 / Tuya TS130F blind/shutter switch | |
| Manufacturer: _TZ3000_bs93npae | |
| Controls exposed in HA device panel: | |
| - motor_reversal (switch) : invert motor direction | |
| - calibration_time (number) : total travel time in 0.1s (e.g. 300 = 30s) | |
| - Start Calibration (button) : starts calibration mode (writes 0 to 0xF001) | |
| - Save Calibration (button) : saves calibration (writes 1 to 0xF001) | |
| """ | |
| from typing import Final | |
| from zigpy.quirks import CustomCluster | |
| from zigpy.quirks.v2 import EntityType, QuirkBuilder | |
| import zigpy.types as t | |
| from zigpy.zcl.clusters.closures import WindowCovering | |
| from zigpy.zcl.foundation import ZCLAttributeDef | |
| ATTR_CURRENT_POSITION_LIFT_PERCENTAGE = 0x0008 | |
| CMD_GO_TO_LIFT_PERCENTAGE = 0x0005 | |
| class MotorMode(t.enum8): | |
| """Tuya motor mode.""" | |
| STRONG_MOTOR = 0x00 | |
| WEAK_MOTOR = 0x01 | |
| class CalibrationMode(t.enum8): | |
| """Tuya calibration mode.""" | |
| CALIBRATE = 0x00 | |
| STORE = 0x01 | |
| class MotorReversal(t.enum8): | |
| """Tuya motor reversal.""" | |
| DEFAULT = 0x00 | |
| REVERSED = 0x01 | |
| class TuyaCoveringCluster(CustomCluster, WindowCovering): | |
| """Tuya TS130F window covering cluster with calibration attributes.""" | |
| class AttributeDefs(WindowCovering.AttributeDefs): | |
| """Attribute definitions.""" | |
| motor_mode: Final = ZCLAttributeDef(id=0x8000, type=MotorMode) | |
| tuya_moving_state: Final = ZCLAttributeDef(id=0xF000, type=t.enum8) | |
| calibration: Final = ZCLAttributeDef(id=0xF001, type=CalibrationMode) | |
| motor_reversal: Final = ZCLAttributeDef(id=0xF002, type=MotorReversal) | |
| calibration_time: Final = ZCLAttributeDef(id=0xF003, type=t.uint16_t) | |
| def _update_attribute(self, attrid, value): | |
| if attrid == ATTR_CURRENT_POSITION_LIFT_PERCENTAGE: | |
| # Tuya reports 0=open, HA expects 0=closed | |
| value = 100 - value | |
| super()._update_attribute(attrid, value) | |
| async def command( | |
| self, command_id, *args, manufacturer=None, expect_reply=True, tsn=None | |
| ): | |
| """Invert lift percentage when sending commands.""" | |
| if command_id == CMD_GO_TO_LIFT_PERCENTAGE: | |
| percent = args[0] | |
| percent = 100 - percent | |
| return await super().command(command_id, percent) | |
| return await super().command( | |
| command_id, | |
| *args, | |
| manufacturer=manufacturer, | |
| expect_reply=expect_reply, | |
| tsn=tsn, | |
| ) | |
| ( | |
| QuirkBuilder("_TZ3000_bs93npae", "TS130F") | |
| .replaces(TuyaCoveringCluster) | |
| # Motor reversal: switch ON = reversed, OFF = normal | |
| .switch( | |
| "motor_reversal", | |
| WindowCovering.cluster_id, | |
| on_value=MotorReversal.REVERSED, | |
| off_value=MotorReversal.DEFAULT, | |
| translation_key="motor_reversal", | |
| fallback_name="Motor Reversal", | |
| entity_type=EntityType.CONFIG, | |
| ) | |
| # Total motor travel time in 0.1s units | |
| .number( | |
| "calibration_time", | |
| WindowCovering.cluster_id, | |
| min_value=0, | |
| max_value=60000, | |
| step=1, | |
| unit="0.1s", | |
| translation_key="calibration_time", | |
| fallback_name="Calibration Time", | |
| entity_type=EntityType.CONFIG, | |
| ) | |
| # Button: start calibration (writes 0 to calibration attribute) | |
| .write_attr_button( | |
| "calibration", | |
| CalibrationMode.CALIBRATE, | |
| WindowCovering.cluster_id, | |
| unique_id_suffix="start", | |
| translation_key="calibration_start", | |
| fallback_name="Start Calibration", | |
| entity_type=EntityType.CONFIG, | |
| ) | |
| # Button: save calibration (writes 1 to calibration attribute) | |
| .write_attr_button( | |
| "calibration", | |
| CalibrationMode.STORE, | |
| WindowCovering.cluster_id, | |
| unique_id_suffix="save", | |
| translation_key="calibration_save", | |
| fallback_name="Save Calibration", | |
| entity_type=EntityType.CONFIG, | |
| ) | |
| .add_to_registry() | |
| ) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment