diff --git a/custom_components/localtuya/const.py b/custom_components/localtuya/const.py index bd8a5d3..ff21ff9 100644 --- a/custom_components/localtuya/const.py +++ b/custom_components/localtuya/const.py @@ -41,11 +41,23 @@ CONF_FAN_SPEED_HIGH = "fan_speed_high" # sensor CONF_SCALING = "scaling" +# vacuum +CONF_IDLE_STATUS_VALUE = "idle_status_value" +CONF_RETURNING_STATUS_VALUE = "returning_status_value" +CONF_DOCKED_STATUS_VALUE = "docked_status_value" +CONF_BATTERY_DP = "battery_dp" +CONF_MODE_DP = "mode_dp" +CONF_MODES = "modes" +CONF_FAN_SPEED_DP = "fan_speed_dp" +CONF_FAN_SPEEDS = "fan_speeds" +CONF_CLEAN_TIME_DP = "clean_time_dp" +CONF_CLEAN_AREA_DP = "clean_area_dp" + DATA_DISCOVERY = "discovery" DOMAIN = "localtuya" # Platforms in this list must support config flows -PLATFORMS = ["binary_sensor", "cover", "fan", "light", "sensor", "switch"] +PLATFORMS = ["binary_sensor", "cover", "fan", "light", "sensor", "switch", "vacuum"] TUYA_DEVICE = "tuya_device" diff --git a/custom_components/localtuya/translations/en.json b/custom_components/localtuya/translations/en.json index 6ce233a..027d8be 100644 --- a/custom_components/localtuya/translations/en.json +++ b/custom_components/localtuya/translations/en.json @@ -60,6 +60,16 @@ "scaling": "Scaling Factor", "state_on": "On Value", "state_off": "Off Value", + "idle_status_value": "Idle Status (comma-separated)", + "returning_status_value": "Returning Status", + "docked_status_value": "Docked Status (comma-separated)", + "battery_dp": "Battery status DP (Usually 14)", + "mode_dp": "Mode DP (Usually 27)", + "modes": "Modes list [start,pause/stop,return home,others...]", + "fan_speed_dp": "Fan speeds DP (Usually 30)", + "fan_speeds": "Fan speeds list (comma-separated)", + "clean_time_dp": "Clean Time DP (Usually 33)", + "clean_area_dp": "Clean Area DP (Usually 32)", "brightness": "Brightness (only for white color)", "brightness_lower": "Brightness Lower Value", "brightness_upper": "Brightness Upper Value", @@ -112,6 +122,16 @@ "scaling": "Scaling Factor", "state_on": "On Value", "state_off": "Off Value", + "idle_status_value": "Idle Status (comma-separated)", + "returning_status_value": "Returning Status", + "docked_status_value": "Docked Status (comma-separated)", + "battery_dp": "Battery status DP (Usually 14)", + "mode_dp": "Mode DP (Usually 27)", + "modes": "Modes list [start,pause/stop,return home,others...]", + "fan_speed_dp": "Fan speeds DP (Usually 30)", + "fan_speeds": "Fan speeds list (comma-separated)", + "clean_time_dp": "Clean Time DP (Usually 33)", + "clean_area_dp": "Clean Area DP (Usually 32)", "brightness": "Brightness (only for white color)", "brightness_lower": "Brightness Lower Value", "brightness_upper": "Brightness Upper Value", diff --git a/custom_components/localtuya/vacuum.py b/custom_components/localtuya/vacuum.py new file mode 100644 index 0000000..4a5c9b5 --- /dev/null +++ b/custom_components/localtuya/vacuum.py @@ -0,0 +1,218 @@ +"""Platform to locally control Tuya-based vacuum devices.""" +import logging +from functools import partial + +import voluptuous as vol +from homeassistant.components.vacuum import ( + DOMAIN, + STATE_CLEANING, + STATE_DOCKED, + STATE_IDLE, + STATE_RETURNING, + SUPPORT_BATTERY, + SUPPORT_FAN_SPEED, + SUPPORT_PAUSE, + SUPPORT_RETURN_HOME, + SUPPORT_START, + SUPPORT_STATE, + SUPPORT_STATUS, + SUPPORT_STOP, + StateVacuumEntity, +) + +from .common import LocalTuyaEntity, async_setup_entry + +from .const import ( + CONF_IDLE_STATUS_VALUE, + CONF_RETURNING_STATUS_VALUE, + CONF_DOCKED_STATUS_VALUE, + CONF_BATTERY_DP, + CONF_MODE_DP, + CONF_MODES, + CONF_FAN_SPEED_DP, + CONF_FAN_SPEEDS, + CONF_CLEAN_TIME_DP, + CONF_CLEAN_AREA_DP +) + +_LOGGER = logging.getLogger(__name__) + +CLEAN_TIME = "clean_time" +CLEAN_AREA = "clean_area" +MODES_LIST = "cleaning_mode_list" +MODE = "cleaning_mode" + +DEFAULT_IDLE_STATUS = "standby,sleep" +DEFAULT_RETURNING_STATUS = "docking" +DEFAULT_DOCKED_STATUS = "charging,chargecompleted" +DEFAULT_MODES = "smart,standby,chargego,wall_follow,spiral,single" +DEFAULT_FAN_SPEEDS = "low,normal,high" + +def flow_schema(dps): + """Return schema used in config flow.""" + return { + vol.Required(CONF_IDLE_STATUS_VALUE, default=DEFAULT_IDLE_STATUS): str, + vol.Required(CONF_DOCKED_STATUS_VALUE, default=DEFAULT_DOCKED_STATUS): str, + vol.Optional(CONF_RETURNING_STATUS_VALUE, default=DEFAULT_RETURNING_STATUS): str, + vol.Optional(CONF_BATTERY_DP): vol.In(dps), + vol.Optional(CONF_MODE_DP): vol.In(dps), + vol.Optional(CONF_MODES, default=DEFAULT_MODES): str, + vol.Optional(CONF_FAN_SPEED_DP): vol.In(dps), + vol.Optional(CONF_FAN_SPEEDS, default=DEFAULT_FAN_SPEEDS): str, + vol.Optional(CONF_CLEAN_TIME_DP): vol.In(dps), + vol.Optional(CONF_CLEAN_AREA_DP): vol.In(dps), + } + + +class LocaltuyaVacuum(LocalTuyaEntity, StateVacuumEntity): + """Tuya vacuum device.""" + + def __init__(self, device, config_entry, switchid, **kwargs): + """Initialize a new LocaltuyaVacuum.""" + super().__init__(device, config_entry, switchid ,_LOGGER, **kwargs) + self._state = None + self._battery_level = None + self._attrs = {} + + self._idle_status_list = [] + if self.has_config(CONF_IDLE_STATUS_VALUE): + self._idle_status_list = self._config[CONF_IDLE_STATUS_VALUE].split(",") + + self._modes_list = [] + if self.has_config(CONF_MODES): + self._modes_list = self._config[CONF_MODES].split(",") + self._attrs[MODES_LIST] = self._modes_list + if len(self._modes_list) >= 3: + self._start_mode = self._modes_list[0] + self._pause_mode = self._modes_list[1] + self._return_mode = self._modes_list[2] + + self._docked_status_list = [] + if self.has_config(CONF_DOCKED_STATUS_VALUE): + self._docked_status_list = self._config[CONF_DOCKED_STATUS_VALUE].split(",") + + self._fan_speed_list = [] + if self.has_config(CONF_FAN_SPEEDS): + self._fan_speed_list = self._config[CONF_FAN_SPEEDS].split(",") + + self._fan_speed = "" + self._cleaning_mode = "" + + print("Initialized vacuum [{}]".format(self.name)) + + @property + def supported_features(self): + """Flag supported features.""" + supported_features = SUPPORT_START | SUPPORT_PAUSE | SUPPORT_STOP | SUPPORT_STATUS | SUPPORT_STATE + + if self.has_config(CONF_RETURNING_STATUS_VALUE): + supported_features = supported_features | SUPPORT_RETURN_HOME + if self.has_config(CONF_FAN_SPEED_DP): + supported_features = supported_features | SUPPORT_FAN_SPEED + if self.has_config(CONF_BATTERY_DP): + supported_features = supported_features | SUPPORT_BATTERY + + return supported_features + + @property + def state(self): + """Return the vacuum state.""" + return self._state + + @property + def battery_level(self): + """Return the current battery level.""" + return self._battery_level + + @property + def device_state_attributes(self): + """Return the specific state attributes of this vacuum cleaner.""" + return self._attrs + + @property + def fan_speed(self): + """Return the current fan speed.""" + return self._fan_speed + + @property + def fan_speed_list(self) -> list: + """Return the list of available fan speeds.""" + return self._fan_speed_list + + @property + def error(self): + """Return error message.""" + return "" + + async def async_start(self, **kwargs): + """Turn the vacuum on and start cleaning.""" + await self._device.set_dp(self._start_mode, self._config[CONF_MODE_DP]) + + async def async_pause(self, **kwargs): + """Stop the vacuum cleaner, do not return to base.""" + if self._pause_mode: + await self._device.set_dp(self._pause_mode, self._config[CONF_MODE_DP]) + else: + _LOGGER.error("Missing command for pause in commands set.") + + async def async_return_to_base(self, **kwargs): + """Set the vacuum cleaner to return to the dock.""" + if self._return_mode: + await self._device.set_dp(self._return_mode, self._config[CONF_MODE_DP]) + else: + _LOGGER.error("Missing command for pause in commands set.") + + async def async_stop(self, **kwargs): + """Turn the vacuum off stopping the cleaning and returning home.""" + self.async_pause() + + async def async_clean_spot(self, **kwargs): + """Perform a spot clean-up.""" + return None + + async def async_locate(self, **kwargs): + """Locate the vacuum cleaner.""" + return None + + async def async_set_fan_speed(self, **kwargs): + """Set the fan speed.""" + fan_speed = kwargs["fan_speed"] + await self._device.set_dp(fan_speed, self._config[CONF_FAN_SPEED_DP]) + + async def async_send_command(self, command, params=None, **kwargs): + """Send a command to a vacuum cleaner.""" + if command == "set_mode" and 'mode' in params: + mode = params['mode'] + await self._device.set_dp(mode, self._config[CONF_MODE_DP]) + + def status_updated(self): + """Device status was updated.""" + state_value = str(self.dps(self._dp_id)) + if state_value in self._idle_status_list: + self._state = STATE_IDLE + elif state_value in self._docked_status_list: + self._state = STATE_DOCKED + elif state_value == self._config[CONF_RETURNING_STATUS_VALUE]: + self._state = STATE_RETURNING + else: + self._state = STATE_CLEANING + + if self.has_config(CONF_BATTERY_DP): + self._battery_level = self.dps_conf(CONF_BATTERY_DP) + + self._cleaning_mode = "" + if self.has_config(CONF_MODES): + self._cleaning_mode = self.dps_conf(CONF_MODE_DP) + self._attrs[MODE] = self._cleaning_mode + + self._fan_speed = "" + if self.has_config(CONF_FAN_SPEEDS): + self._fan_speed = self.dps_conf(CONF_FAN_SPEED_DP) + + if self.has_config(CONF_CLEAN_TIME_DP): + self._attrs[CLEAN_TIME] = self.dps_conf(CONF_CLEAN_TIME_DP) + + if self.has_config(CONF_CLEAN_AREA_DP): + self._attrs[CLEAN_AREA] = self.dps_conf(CONF_CLEAN_AREA_DP) + +async_setup_entry = partial(async_setup_entry, DOMAIN, LocaltuyaVacuum, flow_schema) \ No newline at end of file