Skip to content

Commit

Permalink
Add alarm support (#135)
Browse files Browse the repository at this point in the history
  • Loading branch information
xZetsubou committed Feb 9, 2024
1 parent 800ef6f commit 31a0326
Show file tree
Hide file tree
Showing 13 changed files with 335 additions and 7 deletions.
139 changes: 139 additions & 0 deletions custom_components/localtuya/alarm_control_panel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
"""Platform to present any Tuya DP as a Alarm."""
from enum import StrEnum
import logging
from functools import partial
from .config_flow import _col_to_select

import voluptuous as vol
from homeassistant.helpers import selector
from homeassistant.components.alarm_control_panel import (
DOMAIN,
AlarmControlPanelEntity,
CodeFormat,
AlarmControlPanelEntityFeature,
)
from homeassistant.const import (
STATE_UNKNOWN,
STATE_ALARM_DISARMED,
STATE_ALARM_ARMED_HOME,
STATE_ALARM_ARMED_AWAY,
STATE_ALARM_ARMED_NIGHT,
STATE_ALARM_ARMED_VACATION,
STATE_ALARM_ARMED_CUSTOM_BYPASS,
STATE_ALARM_PENDING,
STATE_ALARM_ARMING,
STATE_ALARM_DISARMING,
STATE_ALARM_TRIGGERED,
)

from .common import LocalTuyaEntity, async_setup_entry
from .const import CONF_ALARM_SUPPORTED_STATES

_LOGGER = logging.getLogger(__name__)

DEFAULT_PRECISION = 2


class TuyaMode(StrEnum):
DISARMED = "arm"
ARM = "disarmed"
HOME = "home"
SOS = "sos"


DEFAULT_SUPPORTED_MODES = {
STATE_ALARM_DISARMED: TuyaMode.DISARMED,
STATE_ALARM_ARMED_AWAY: TuyaMode.ARM,
STATE_ALARM_ARMED_HOME: TuyaMode.HOME,
STATE_ALARM_TRIGGERED: TuyaMode.SOS,
}


def flow_schema(dps):
"""Return schema used in config flow."""
return {
vol.Optional(
CONF_ALARM_SUPPORTED_STATES, default=DEFAULT_SUPPORTED_MODES
): selector.ObjectSelector(),
}


class LocalTuyaAlarmControlPanel(LocalTuyaEntity, AlarmControlPanelEntity):
"""Representation of a Tuya Alarm."""

_supported_modes = {}

def __init__(
self,
device,
config_entry,
dpid,
**kwargs,
):
"""Initialize the Tuya Alarm."""
super().__init__(device, config_entry, dpid, _LOGGER, **kwargs)
self._state = None
self._changed_by = None

# supported modes
if supported_modes := self._config.get(CONF_ALARM_SUPPORTED_STATES, {}):
if TuyaMode.HOME in supported_modes:
self._attr_supported_features |= AlarmControlPanelEntityFeature.ARM_HOME
if TuyaMode.ARM in supported_modes:
self._attr_supported_features |= AlarmControlPanelEntityFeature.ARM_AWAY
if TuyaMode.SOS in supported_modes:
self._attr_supported_features |= AlarmControlPanelEntityFeature.TRIGGER

self._state_tuya_to_ha: dict[str, str] = {
v: k for k, v in supported_modes.items()
}

@property
def state(self):
"""Return Alarm state."""
return self._state_tuya_to_ha.get(self._state, None)

@property
def code_format(self) -> CodeFormat | None:
"""Code format or None if no code is required."""
return None # self._attr_code_format

@property
def changed_by(self) -> str | None:
"""Last change triggered by."""
return None # self._attr_changed_by

@property
def code_arm_required(self) -> bool:
"""Whether the code is required for arm actions."""
return True # self._attr_code_arm_required

async def async_alarm_disarm(self, code: str | None = None) -> None:
"""Send disarm command."""
await self._device.set_dp(self._dp_id, TuyaMode.DISARMED)

async def async_alarm_arm_home(self, code: str | None = None) -> None:
"""Send arm home command."""
await self._device.set_dp(self._dp_id, TuyaMode.HOME)

async def async_alarm_arm_away(self, code: str | None = None) -> None:
"""Send arm away command."""
await self._device.set_dp(self._dp_id, TuyaMode.ARM)

async def async_alarm_trigger(self, code: str | None = None) -> None:
"""Send alarm trigger command."""
await self._device.set_dp(self._dp_id, TuyaMode.SOS)

def status_updated(self):
"""Device status was updated."""
super().status_updated()

# No need to restore state for a AlarmControlPanel
async def restore_state_when_connected(self):
"""Do nothing for a AlarmControlPanel."""
return


async_setup_entry = partial(
async_setup_entry, DOMAIN, LocalTuyaAlarmControlPanel, flow_schema
)
4 changes: 4 additions & 0 deletions custom_components/localtuya/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

# Platforms in this list must support config flows
PLATFORMS = {
"Alarm Control Panel": Platform.ALARM_CONTROL_PANEL,
"Binary Sensor": Platform.BINARY_SENSOR,
"Button": Platform.BUTTON,
"Climate": Platform.CLIMATE,
Expand Down Expand Up @@ -59,6 +60,9 @@
CONF_RESET_DPIDS = "reset_dpids"
CONF_PASSIVE_ENTITY = "is_passive_entity"

# ALARM
CONF_ALARM_SUPPORTED_STATES = "alarm_supported_states"

# Binary_sensor, Siren
CONF_STATE_ON = "state_on"

Expand Down
2 changes: 1 addition & 1 deletion custom_components/localtuya/core/ha_entities/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@

# The supported PLATFORMS [ Platform: Data ]
DATA_PLATFORMS = {
# Platform.ALARM_CONTROL_PANEL: ALARMS,
Platform.ALARM_CONTROL_PANEL: ALARMS,
Platform.BINARY_SENSOR: BINARY_SENSORS,
Platform.BUTTON: BUTTONS,
Platform.CLIMATE: CLIMATES,
Expand Down
15 changes: 15 additions & 0 deletions custom_components/localtuya/core/ha_entities/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ class DPCode(StrEnum):
ADD_ELE1 = "add_ele1"
ADD_ELE2 = "add_ele2"
AIR_QUALITY = "air_quality"
ALARM_DELAY_TIME = "alarm_delay_time"
ALARM_LOCK = "alarm_lock"
ALARM_MESSAGE = "alarm_message"
ALARM_RINGTONE = "alarm_ringtone"
Expand Down Expand Up @@ -215,6 +216,7 @@ class DPCode(StrEnum):
DECIBEL_SWITCH = "decibel_switch"
DEHUMIDITY_SET_ENUM = "dehumidify_set_enum"
DEHUMIDITY_SET_VALUE = "dehumidify_set_value"
DELAY_SET = "delay_set"
DEVICE_STATE1 = "device_state1"
DEVICE_STATE2 = "device_state2"
DISINFECTION = "disinfection"
Expand Down Expand Up @@ -306,6 +308,7 @@ class DPCode(StrEnum):
NORMAL_OPEN_SWITCH = "normal_open_switch"
OPPOSITE = "opposite"
OPTIMUMSTART = "optimumstart"
OTHEREVENT = "OtherEvent"
OVERCHARGE_SWITCH = "overcharge_switch"
OXYGEN = "oxygen" # Oxygen bar
PAUSE = "pause"
Expand Down Expand Up @@ -338,6 +341,7 @@ class DPCode(StrEnum):
POSITION = "position"
POWDER_SET = "powder_set" # Powder
POWER = "power"
POWEREVENT = "PowerEvent"
POWER_GO = "power_go"
POWER_TYPE = "power_type"
POWER_TYPE1 = "power_type1"
Expand Down Expand Up @@ -418,6 +422,9 @@ class DPCode(StrEnum):
START = "start" # Start
STATUS = "status"
STERILIZATION = "sterilization" # Sterilization
SUB_CLASS = "sub_class"
SUB_STATE = "sub_state"
SUB_TYPE = "sub_type"
SUCTION = "suction"
SWING = "swing" # Swing mode
SWITCH = "switch" # Switch
Expand All @@ -437,13 +444,19 @@ class DPCode(StrEnum):
SWITCH_6 = "switch_6" # Switch 6
SWITCH_7 = "switch_7" # Switch 7
SWITCH_8 = "switch_8" # Switch 8
SWITCH_ALARM_CALL = "switch_alarm_call"
SWITCH_ALARM_LIGHT = "switch_alarm_light"
SWITCH_ALARM_PROPEL = "switch_alarm_propel"
SWITCH_ALARM_SMS = "switch_alarm_sms"
SWITCH_ALARM_SOUND = "switch_alarm_sound"
SWITCH_BACKLIGHT = "switch_backlight" # Backlight switch
SWITCH_CHARGE = "switch_charge"
SWITCH_CONTROLLER = "switch_controller"
SWITCH_DISTURB = "switch_disturb"
SWITCH_FAN = "switch_fan"
SWITCH_HORIZONTAL = "switch_horizontal" # Horizontal swing flap switch
SWITCH_KB_LIGHT = "switch_kb_light"
SWITCH_KB_SOUND = "switch_kb_sound"
SWITCH_LED = "switch_led" # Switch
SWITCH_LED_1 = "switch_led_1"
SWITCH_LED_2 = "switch_led_2"
Expand Down Expand Up @@ -556,3 +569,5 @@ class DPCode(StrEnum):
WORK_POWER = "work_power"
WORK_STATE = "work_state"
WORK_STATUS = "work_status"
ZONE_ATTRIBUTE = "zone_attribute"
ZONE_NUMBER = "zone_number"
25 changes: 25 additions & 0 deletions custom_components/localtuya/core/ha_entities/numbers.py
Original file line number Diff line number Diff line change
Expand Up @@ -620,6 +620,31 @@ def localtuya_numbers(_min, _max, _step=1, _scale=1, unit=None) -> dict:
custom_configs=localtuya_numbers(1, 9),
),
),
# Alarm Host
# https://developer.tuya.com/en/docs/iot/categorymal?id=Kaiuz33clqxaf
"mal": (
LocalTuyaEntity(
id=DPCode.DELAY_SET,
name="Delay Setting",
custom_configs=localtuya_numbers(0, 65535),
icon="mdi:clock-outline",
entity_category=EntityCategory.CONFIG,
),
LocalTuyaEntity(
id=DPCode.ALARM_TIME,
name="Duration",
custom_configs=localtuya_numbers(0, 65535),
icon="mdi:alarm",
entity_category=EntityCategory.CONFIG,
),
LocalTuyaEntity(
id=DPCode.ALARM_DELAY_TIME,
name="Delay Alarm",
custom_configs=localtuya_numbers(0, 65535),
icon="mdi:history",
entity_category=EntityCategory.CONFIG,
),
),
}

# Wireless Switch # also can come as knob switch.
Expand Down
59 changes: 59 additions & 0 deletions custom_components/localtuya/core/ha_entities/selects.py
Original file line number Diff line number Diff line change
Expand Up @@ -832,6 +832,65 @@ def localtuya_selector(options):
),
),
),
# Alarm Host
# https://developer.tuya.com/en/docs/iot/categorymal?id=Kaiuz33clqxaf
"mal": (
LocalTuyaEntity(
id=DPCode.ZONE_ATTRIBUTE,
entity_category=EntityCategory.CONFIG,
name="Zone Attribute",
custom_configs=localtuya_selector(
{
"MODE_HOME_ARM": "Home Arm",
"MODE_ARM": "Arm",
"MODE_24": "24H",
"MODE_DOORBELL": "Doorbell",
"MODE_24_SILENT": "Sielnt",
"HOME_ARM_NO_DELAY": "Home, Arm No delay",
"ARM_NO_DELAY": "Arm No delay",
}
),
),
LocalTuyaEntity(
id=DPCode.MASTER_STATE,
entity_category=EntityCategory.CONFIG,
name="Host Status",
custom_configs=localtuya_selector({"normal": "Normal", "alarm": "Alarm"}),
),
LocalTuyaEntity(
id=DPCode.SUB_CLASS,
entity_category=EntityCategory.CONFIG,
name="Sub-device category",
custom_configs=localtuya_selector(
{
"remote_controller": "Remote Controller",
"detector": "Detector",
"socket": "Socket",
}
),
),
LocalTuyaEntity(
id=DPCode.SUB_TYPE,
entity_category=EntityCategory.CONFIG,
name="Sub-device type",
custom_configs=localtuya_selector(
{
"OTHER": "Other",
"DOOR": "Door",
"PIR": "Pir",
"SOS": "SoS",
"ROOM": "Room",
"WINDOW": "Window",
"BALCONY": "Balcony",
"FENCE": "Fence",
"SMOKE": "Smoke",
"GAS": "Gas",
"CO": "CO",
"WATER": "Water",
}
),
),
),
}
# Wireless Switch # also can come as knob switch.
# https://developer.tuya.com/en/docs/iot/wxkg?id=Kbeo9t3ryuqm5
Expand Down
24 changes: 24 additions & 0 deletions custom_components/localtuya/core/ha_entities/sensors.py
Original file line number Diff line number Diff line change
Expand Up @@ -1271,6 +1271,30 @@ def localtuya_sensor(unit_of_measurement=None, scale_factor: float = None) -> di
),
*BATTERY_SENSORS,
),
# Alarm Host
# https://developer.tuya.com/en/docs/iot/categorymal?id=Kaiuz33clqxaf
"mal": (
LocalTuyaEntity(
id=DPCode.SUB_STATE,
name="Sub-Device State",
entity_category=EntityCategory.DIAGNOSTIC,
),
LocalTuyaEntity(
id=DPCode.POWEREVENT,
name="Power Event",
entity_category=EntityCategory.DIAGNOSTIC,
),
LocalTuyaEntity(
id=DPCode.ZONE_NUMBER,
name="Zone Number",
entity_category=EntityCategory.DIAGNOSTIC,
),
LocalTuyaEntity(
id=DPCode.OTHEREVENT,
name="Other Event",
entity_category=EntityCategory.DIAGNOSTIC,
),
),
}

# Socket (duplicate of `kg`)
Expand Down

0 comments on commit 31a0326

Please sign in to comment.