Fix trigger condition and alarm message in Tuya Alarm (#132963)

Co-authored-by: Franck Nijhof <git@frenck.dev>
This commit is contained in:
asafhas 2025-05-26 22:05:09 +03:00 committed by GitHub
parent 0b6ea36e24
commit fd4dafaac5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 53 additions and 4 deletions

View File

@ -2,6 +2,8 @@
from __future__ import annotations
from base64 import b64decode
from dataclasses import dataclass
from enum import StrEnum
from tuya_sharing import CustomerDevice, Manager
@ -18,7 +20,15 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import TuyaConfigEntry
from .const import TUYA_DISCOVERY_NEW, DPCode, DPType
from .entity import TuyaEntity
from .entity import EnumTypeData, TuyaEntity
@dataclass(frozen=True)
class TuyaAlarmControlPanelEntityDescription(AlarmControlPanelEntityDescription):
"""Describe a Tuya Alarm Control Panel entity."""
master_state: DPCode | None = None
alarm_msg: DPCode | None = None
class Mode(StrEnum):
@ -30,6 +40,13 @@ class Mode(StrEnum):
SOS = "sos"
class State(StrEnum):
"""Alarm states."""
NORMAL = "normal"
ALARM = "alarm"
STATE_MAPPING: dict[str, AlarmControlPanelState] = {
Mode.DISARMED: AlarmControlPanelState.DISARMED,
Mode.ARM: AlarmControlPanelState.ARMED_AWAY,
@ -40,12 +57,14 @@ STATE_MAPPING: dict[str, AlarmControlPanelState] = {
# All descriptions can be found here:
# https://developer.tuya.com/en/docs/iot/standarddescription?id=K9i5ql6waswzq
ALARM: dict[str, tuple[AlarmControlPanelEntityDescription, ...]] = {
ALARM: dict[str, tuple[TuyaAlarmControlPanelEntityDescription, ...]] = {
# Alarm Host
# https://developer.tuya.com/en/docs/iot/categorymal?id=Kaiuz33clqxaf
"mal": (
AlarmControlPanelEntityDescription(
TuyaAlarmControlPanelEntityDescription(
key=DPCode.MASTER_MODE,
master_state=DPCode.MASTER_STATE,
alarm_msg=DPCode.ALARM_MSG,
name="Alarm",
),
)
@ -86,12 +105,14 @@ class TuyaAlarmEntity(TuyaEntity, AlarmControlPanelEntity):
_attr_name = None
_attr_code_arm_required = False
_master_state: EnumTypeData | None = None
_alarm_msg_dpcode: DPCode | None = None
def __init__(
self,
device: CustomerDevice,
device_manager: Manager,
description: AlarmControlPanelEntityDescription,
description: TuyaAlarmControlPanelEntityDescription,
) -> None:
"""Init Tuya Alarm."""
super().__init__(device, device_manager)
@ -111,13 +132,39 @@ class TuyaAlarmEntity(TuyaEntity, AlarmControlPanelEntity):
if Mode.SOS in supported_modes.range:
self._attr_supported_features |= AlarmControlPanelEntityFeature.TRIGGER
# Determine master state
if enum_type := self.find_dpcode(
description.master_state, dptype=DPType.ENUM, prefer_function=True
):
self._master_state = enum_type
# Determine alarm message
if dp_code := self.find_dpcode(description.alarm_msg, prefer_function=True):
self._alarm_msg_dpcode = dp_code
@property
def alarm_state(self) -> AlarmControlPanelState | None:
"""Return the state of the device."""
# When the alarm is triggered, only its 'state' is changing. From 'normal' to 'alarm'.
# The 'mode' doesn't change, and stays as 'arm' or 'home'.
if self._master_state is not None:
if self.device.status.get(self._master_state.dpcode) == State.ALARM:
return AlarmControlPanelState.TRIGGERED
if not (status := self.device.status.get(self.entity_description.key)):
return None
return STATE_MAPPING.get(status)
@property
def changed_by(self) -> str | None:
"""Last change triggered by."""
if self._master_state is not None and self._alarm_msg_dpcode is not None:
if self.device.status.get(self._master_state.dpcode) == State.ALARM:
encoded_msg = self.device.status.get(self._alarm_msg_dpcode)
if encoded_msg:
return b64decode(encoded_msg).decode("utf-16be")
return None
def alarm_disarm(self, code: str | None = None) -> None:
"""Send Disarm command."""
self._send_command(

View File

@ -102,6 +102,7 @@ class DPCode(StrEnum):
ALARM_TIME = "alarm_time" # Alarm time
ALARM_VOLUME = "alarm_volume" # Alarm volume
ALARM_MESSAGE = "alarm_message"
ALARM_MSG = "alarm_msg"
ANGLE_HORIZONTAL = "angle_horizontal"
ANGLE_VERTICAL = "angle_vertical"
ANION = "anion" # Ionizer unit
@ -226,6 +227,7 @@ class DPCode(StrEnum):
LIGHT_MODE = "light_mode"
LOCK = "lock" # Lock / Child lock
MASTER_MODE = "master_mode" # alarm mode
MASTER_STATE = "master_state" # alarm state
MACH_OPERATE = "mach_operate"
MANUAL_FEED = "manual_feed"
MATERIAL = "material" # Material