DavidIlie / tuya-smart-ir-ac

Control Tuya IR based air conditioners from Home Assistant
16 stars 10 forks source link

Need feedback for edited code #8

Open Ovi8392 opened 5 months ago

Ovi8392 commented 5 months ago

All credits goes to ChatGPT, I just tell him what to do.

Fix and improvements made:

Findings over testing:

Big thanks to David! Save my started budget smart home project.


Add to configuration.yaml this code too:

logger: default: warning logs: custom_components.tuya_smart_ir_ac: debug tuya_hack: debug

/homeassistant/custom_components/tuya_smart_ir_ac/climate.py `import logging from typing import Any, Dict

import voluptuous as vol from pprint import pformat

from homeassistant.core import HomeAssistant from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.config_validation import PLATFORM_SCHEMA import homeassistant.helpers.config_validation as cv from homeassistant.components.climate.const import HVACMode from homeassistant.const import UnitOfTemperature, STATE_UNKNOWN from homeassistant.components.climate import ClimateEntity, ClimateEntityFeature

from .const import VALID_MODES from .api import TuyaAPI

_LOGGER = logging.getLogger("tuya_hack")

Define constants for configuration keys

ACCESS_ID = "access_id" ACCESS_SECRET = "access_secret" REMOTE_ID = "remote_id" AC_ID = "ac_id" NAME = "name" SENSOR = "sensor"

Schema for platform configuration

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { vol.Required(ACCESS_ID): cv.string, vol.Required(ACCESS_SECRET): cv.string, vol.Required(REMOTE_ID): cv.string, vol.Required(AC_ID): cv.string, vol.Required(NAME): cv.string, vol.Required(SENSOR): cv.string, } )

def setup_platform( hass: HomeAssistant, config: ConfigType, add_entities: AddEntitiesCallback, discovery_info: DiscoveryInfoType | None = None, ) -> None: """Set up the Tuya thermostat platform.""" _LOGGER.info("Setting up Tuya thermostat platform") climate_config = { "access_id": config[ACCESS_ID], "access_secret": config[ACCESS_SECRET], "remote_id": config[REMOTE_ID], "ac_id": config[AC_ID], "name": config[NAME], "sensor": config[SENSOR] }

_LOGGER.debug(f"Platform configuration: {pformat(climate_config)}")
add_entities([TuyaThermostat(climate_config, hass)])

class TuyaThermostat(ClimateEntity): """Representation of a Tuya Thermostat."""

def __init__(self, climate: Dict[str, Any], hass: HomeAssistant) -> None:
    """Initialize the Tuya thermostat entity."""
    _LOGGER.info("Initializing Tuya thermostat")
    _LOGGER.debug(pformat(climate))
    self._api = TuyaAPI(
        hass,
        climate[ACCESS_ID],
        climate[ACCESS_SECRET],
        climate[AC_ID],
        climate[REMOTE_ID],
    )
    self._sensor_name = climate[SENSOR]
    self._name = climate[NAME]
    self.hass = hass

@property
def name(self) -> str:
    """Return the name of the thermostat."""
    return self._name

@property
def unique_id(self) -> str:
    """Return the unique ID of the thermostat."""
    return f"tuya_thermostat_{self._api.thermostat_device_id}"

@property
def temperature_unit(self) -> str:
    """Return the unit of temperature measurement."""
    return UnitOfTemperature.CELSIUS

@property
def supported_features(self) -> int:
    """Return the supported features of the thermostat."""
    return (
        ClimateEntityFeature.TARGET_TEMPERATURE |
        ClimateEntityFeature.FAN_MODE
    )

@property
def min_temp(self) -> float:
    """Return the minimum temperature that can be set."""
    return 15.0

@property
def max_temp(self) -> float:
    """Return the maximum temperature that can be set."""
    return 30.0

@property
def current_temperature(self) -> float | None:
    """Return the current temperature."""
    sensor_state = self.hass.states.get(self._sensor_name)
    _LOGGER.debug(f"Current sensor state: {sensor_state}")
    if sensor_state and sensor_state.state != STATE_UNKNOWN:
        try:
            return float(sensor_state.state)
        except ValueError:
            _LOGGER.error(f"Invalid sensor state: {sensor_state.state}")
    return float(self._api._temperature) if self._api._temperature else None

@property
def target_temperature(self) -> float | None:
    """Return the temperature we try to reach."""
    return float(self._api._temperature) if self._api._temperature else None

@property
def hvac_mode(self) -> str | None:
    """Return current operation (heat, cool, idle)."""
    if self._api._power == "0":
        return HVACMode.OFF
    return VALID_MODES.get(str(self._api._mode), None)

@property
def hvac_modes(self) -> list[str]:
    """Return the list of available operation modes."""
    return list(VALID_MODES.values())

@property
def fan_mode(self) -> str | None:
    """Return the fan setting."""
    return (
        "Low" if self._api._wind == "1" else
        "Medium" if self._api._wind == "2" else
        "High" if self._api._wind == "3" else
        "Automatic" if self._api._wind == "0" else None
    )

@property
def fan_modes(self) -> list[str]:
    """Return the list of available fan modes."""
    return ["Low", "Medium", "High", "Automatic"]

async def async_set_fan_mode(self, fan_mode: str) -> None:
    """Set new target fan mode."""
    _LOGGER.info(f"Setting fan mode to: {fan_mode}")
    command_map = {
        "Low": "1",
        "Medium": "2",
        "High": "3",
        "Automatic": "0",
    }
    if fan_mode in command_map:
        await self._api.send_command("wind", command_map[fan_mode])
    else:
        _LOGGER.warning(f"Invalid fan mode: {fan_mode}")

async def async_update(self) -> None:
    """Update the state of the thermostat."""
    _LOGGER.info("Updating Tuya thermostat state")
    await self._api.async_update()
    self.async_write_ha_state()

async def async_set_temperature(self, **kwargs: Any) -> None:
    """Set new target temperature."""
    temperature = kwargs.get("temperature")
    if temperature is not None:
        _LOGGER.info(f"Setting target temperature to: {temperature}")
        await self._api.async_set_temperature(float(temperature))

async def async_set_hvac_mode(self, hvac_mode: str) -> None:
    """Set new target operation mode."""
    _LOGGER.info(f"Setting HVAC mode to: {hvac_mode}")
    for mode, mode_name in VALID_MODES.items():
        if hvac_mode == mode_name:
            if mode == "5":
                await self._api.async_power_off()
            else:
                if self._api._power == "0":
                    await self._api.async_power_on()
                await self._api.async_set_hvac_mode(mode)
            break
    else:
        _LOGGER.warning(f"Invalid HVAC mode: {hvac_mode}")

`

/homeassistant/custom_components/tuya_smart_ir_ac/api.py `from tuya_connector import TuyaOpenAPI from .const import VALID_MODES from homeassistant.core import HomeAssistant import logging from pprint import pformat

_LOGGER = logging.getLogger("tuya_hack")

class TuyaAPI: """ Interface to interact with Tuya devices. """

def __init__(
    self,
    hass: HomeAssistant,
    access_id,
    access_secret,
    thermostat_device_id,
    ir_remote_device_id,
):
    """
    Initialize Tuya API.
    """
    self.access_id = access_id
    self.access_secret = access_secret
    self.thermostat_device_id = thermostat_device_id
    self.ir_remote_device_id = ir_remote_device_id
    self.hass = hass

    """
    Change address according used server.
    China Data Center   https://openapi.tuyacn.com
    *** Western America Data Center https://openapi.tuyaus.com
    Eastern America Data Center https://openapi-ueaz.tuyaus.com
    Central Europe Data Center  https://openapi.tuyaeu.com
    Western Europe Data Center  https://openapi-weaz.tuyaeu.com
    India Data Center   https://openapi.tuyain.com
    """
    openapi = TuyaOpenAPI("https://openapi.tuyaus.com", access_id, access_secret)
    openapi.connect()
    self.openapi = openapi

    self._temperature = "0"
    self._mode = "0"
    self._power = "0"
    self._wind = "0"

async def async_init(self):
    """
    Asynchronously initialize Tuya API.
    """
    await self.async_update()

async def async_update(self):
    """
    Asynchronously update status from the device.
    """
    status = await self.get_status()
    if status:
        self._temperature = status.get("temp")
        self._mode = status.get("mode")
        self._power = status.get("power")
        self._wind = status.get("wind")
    _LOGGER.info(f"ASYNC_UPDATE: {status}")

async def async_set_fan_speed(self, fan_speed):
    """
    Asynchronously set fan speed.
    """
    _LOGGER.info(f"Setting fan speed to {fan_speed}")
    await self.send_command("wind", str(fan_speed))

async def async_set_temperature(self, temperature):
    """
    Asynchronously set temperature.
    """
    _LOGGER.info(f"Setting temperature to {temperature}")
    await self.send_command("temp", str(temperature))

async def async_power_on(self):
    """
    Asynchronously turn on the device.
    """
    _LOGGER.info("Turning on")
    await self.send_command("power", "1")

async def async_power_off(self):
    """
    Asynchronously turn off the device.
    """
    _LOGGER.info("Turning off")
    await self.send_command("power", "0")

async def async_set_hvac_mode(self, hvac_mode):
    """
    Asynchronously set HVAC mode.
    """
    _LOGGER.info(f"Setting HVAC mode to {hvac_mode}")
    await self.send_command("mode", str(hvac_mode))

async def get_status(self):
    """
    Asynchronously fetch device status.
    """
    url = f"/v2.0/infrareds/{self.ir_remote_device_id}/remotes/{self.thermostat_device_id}/ac/status"
    _LOGGER.info(f"Fetching status from URL: {url}")
    try:
        data = await self.hass.async_add_executor_job(self.openapi.get, url)
        _LOGGER.debug(f"Full response data: {pformat(data)}")
        if data.get("success"):
            _LOGGER.info(f"GET_STATUS: {data.get('result')}")
            return data.get("result")
        else:
            _LOGGER.warning(f"Failed to fetch status: {data}")
    except Exception as e:
        _LOGGER.error(f"Error fetching status: {e}")
    return None

async def send_command(self, code, value):
    """
    Asynchronously send command to the device.
    """
    url = f"/v2.0/infrareds/{self.ir_remote_device_id}/air-conditioners/{self.thermostat_device_id}/command"
    _LOGGER.info(f"Sending command to URL: {url}")
    _LOGGER.info(f"SEND_COMMAND_CODE_THEN_VAL: {code} {value}")
    try:
        data = await self.hass.async_add_executor_job(
            self.openapi.post,
            url,
            {
                "code": code,
                "value": value,
            },
        )
        _LOGGER.debug(f"Full response data: {pformat(data)}")
        if data.get("success"):
            _LOGGER.info(f"Command {code} with value {value} sent successfully")
        else:
            _LOGGER.warning(f"Failed to send command {code} with value {value}: {data}")
        return data
    except Exception as e:
        _LOGGER.error(f"Error sending command: {e}")
        return False`

/homeassistant/custom_components/tuya_smart_ir_ac/const.py `from homeassistant.components.climate.const import HVACMode import logging

_LOGGER = logging.getLogger(name)

VALID_MODES = { "0": HVACMode.COOL, "1": HVACMode.HEAT, "2": HVACMode.AUTO, "3": HVACMode.FAN_ONLY, "4": HVACMode.DRY, "5": HVACMode.OFF, }

_LOGGER.debug(f"Valid HVAC modes: {VALID_MODES}")`

eitan-github commented 5 months ago

Need Help.... I understood that you solved the problem of - Two+ units can be added I couldn't figure out how to fix the problem according to your solution. What to copy to my files Thanks

Ovi8392 commented 5 months ago

Need Help.... I understood that you solved the problem of - Two+ units can be added I couldn't figure out how to fix the problem according to your solution. What to copy to my files Thanks

I'm new on Git so don't know how things around here works.

https://github.com/Ovi8392/tuya_custom/tree/DavidIlie-/-tuya-smart-ir-ac-/-(edited-by-me)

Files you interested in:

init.py api.py climate.py const.py manifest.json

to add in configuration.yaml

climate:

end of part to add in configuration.yaml

Remember change in api.py correct server you use 
openapi = TuyaOpenAPI("https://openapi.tuyaus.com", access_id, access_secret)

Ignore my project [tuya_custom] and its files, it's my playground, I add Fan with it, but still testing.
krotos007 commented 4 months ago

Need Help.... I understood that you solved the problem of - Two+ units can be added I couldn't figure out how to fix the problem according to your solution. What to copy to my files Thanks

I'm new on Git so don't know how things around here works.

https://github.com/Ovi8392/tuya_custom/tree/DavidIlie-/-tuya-smart-ir-ac-/-(edited-by-me)

Files you interested in:

init.py api.py climate.py const.py manifest.json

to add in configuration.yaml

climate:

  • platform: tuya_smart_ir_ac name: "Name AC 1" sensor: "sensor.temperature_humidity_sensor_temperature" ### Replace with your sensor access_id: "xxxxx" ### from Tuya developer site access_secret: "xxxxx" ### from Tuya developer site remote_id: "xxxxx" ### Smart IR device ac_id: "xxxxx" ### Remote created with Smart IR
  • platform: tuya_smart_ir_ac name: "Name AC 2" sensor: "sensor.temperature_humidity_sensor_temperature" ### Replace with your sensor access_id: "xxxxx" access_secret: "xxxxx" remote_id: "xxxxx" ac_id: "xxxxx"

logger: default: info logs: custom_components.tuya_smart_ir_ac: debug tuya_hack: debug

end of part to add in configuration.yaml

Remember change in api.py correct server you use 
openapi = TuyaOpenAPI("https://openapi.tuyaus.com", access_id, access_secret)

Ignore my project [tuya_custom] and its files, it's my playground, I add Fan with it, but still testing.

tanks man this solved my problem, now i can add more then one ac wihtout any problem