diff --git a/custom_components/sonoff/climate.py b/custom_components/sonoff/climate.py index 06d8a53..cec397a 100644 --- a/custom_components/sonoff/climate.py +++ b/custom_components/sonoff/climate.py @@ -317,3 +317,120 @@ async def async_set_temperature( params["targetTemp"] = temperature await self.ewelink.send(self.device, params) + + +class XThermostatTRVZB(XEntity, ClimateEntity): + params = {"switch", "curTargetTemp", "manTargetTemp", "autoTargetTemp", "ecoTargetTemp", "temperature", "workMode", "workState"} + + # @bwp91 https://github.com/AlexxIT/SonoffLAN/issues/358 + _attr_hvac_modes = [HVACMode.OFF, HVACMode.HEAT, HVACMode.AUTO] + _attr_max_temp = 45 + _attr_min_temp = 5 + _attr_preset_modes = ["Manual", "Off", "Auto"] +# _attr_preset_mode = "manual" + _attr_temperature_unit = UnitOfTemperature.CELSIUS + _attr_target_temperature_step = 0.5 + + # https://developers.home-assistant.io/blog/2024/01/24/climate-climateentityfeatures-expanded + if (MAJOR_VERSION, MINOR_VERSION) >= (2024, 2): + _attr_supported_features = ( + ClimateEntityFeature.TARGET_TEMPERATURE + | ClimateEntityFeature.PRESET_MODE + | ClimateEntityFeature.TURN_ON + | ClimateEntityFeature.TURN_OFF + ) + _enable_turn_on_off_backwards_compatibility = False + else: + _attr_supported_features = ( + ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.PRESET_MODE + ) + + def TargetTempNameByMode(self): + wm = self.preset_modes.index(self._attr_preset_mode) + + if wm==0: + return "manTargetTemp" + elif wm==1: + return "ecoTargetTemp" + elif wm==2: + return "autoTargetTemp" + + return "none" + + def set_state(self, params: dict): + cache = self.device["params"] + if cache != params: + cache.update(params) + + #if cache["switch"] == "on": + # workState: 1=heating, 2=auto + # self._attr_hvac_mode = self.hvac_modes[int(cache["workState"])] + #else: + # self._attr_hvac_mode = HVACMode.OFF + + if "workMode" in cache: + wm = int(cache["workMode"]) + wm_to_hvac_mode = [HVACMode.HEAT, HVACMode.OFF, HVACMode.AUTO] + if wm in [0,1,2]: + self._attr_hvac_mode = wm_to_hvac_mode[wm] + + self._attr_preset_mode = self.preset_modes[wm] + + if "curTargetTemp" in cache: + self._attr_target_temperature = cache["curTargetTemp"]*0.1 + + if "temperature" in cache: + self._attr_current_temperature = cache["temperature"]*0.1 + + async def async_set_hvac_mode(self, hvac_mode: str) -> None: + i = self.hvac_modes.index(hvac_mode) + + #print(hvac_mode, i) + # hvac = (off , heat, auto) + # workMode = ("manual", "off", "auto") + hvac_to_wm = [1, 0, 2] + if i in [0,1,2]: + # convert hvac to work mode + wm = hvac_to_wm[i] + else: + # off + wm = 1 + + params = {} + params["workMode"]=str(wm) + + await self.ewelink.send_cloud(self.device, params) + + async def async_set_preset_mode(self, preset_mode: str) -> None: + params = {} + wm = self.preset_modes.index(preset_mode) + params["workMode"]=str(wm) + + #print(params) + + await self.ewelink.send_cloud(self.device, params) + + async def async_set_temperature( + self, + temperature: float = None, + hvac_mode: str = None, + preset_mode: str = None, + **kwargs + ) -> None: + if hvac_mode is None: + params = {} + elif hvac_mode is HVACMode.OFF: + params = {"switch": "off"} + else: + i = self.hvac_modes.index(hvac_mode) + params = {"switch": "on", "workState": i} + + if preset_mode is not None: + params["workMode"] = self.preset_modes.index(preset_mode) + + if temperature is not None: + TargetTemp = self.TargetTempNameByMode() + params[TargetTemp] = temperature*10 + print(TargetTemp) + + await self.ewelink.send(self.device, params) \ No newline at end of file diff --git a/custom_components/sonoff/core/devices.py b/custom_components/sonoff/core/devices.py index d62df1a..bbaf22c 100644 --- a/custom_components/sonoff/core/devices.py +++ b/custom_components/sonoff/core/devices.py @@ -27,7 +27,7 @@ XZigbeeMotion, ) from ..button import XT5Button -from ..climate import XClimateNS, XClimateTH, XThermostat +from ..climate import XClimateNS, XClimateTH, XThermostat, XThermostatTRVZB from ..core.entity import XEntity from ..cover import XCover, XCover91, XCoverDualR3, XZigbeeCover from ..fan import XDiffuserFan, XFan, XFanDualR3, XToggleFan @@ -64,9 +64,11 @@ XTemperatureTH, XUnknown, XWiFiDoorBattery, + XHexVoltageTRVZB, ) from ..switch import ( XBoolSwitch, + XBoolSwitchTRVZB, XDetach, XSwitch, XSwitchPOWR3, @@ -453,6 +455,7 @@ def spec(cls, base: str = None, enabled: bool = None, **kwargs) -> type: ], # https://github.com/AlexxIT/SonoffLAN/issues/1166 7016: [XHumanSensor, XLightSensor, XSensitivity, ZRSSI], # SNZB-06P 7017: [ + XThermostatTRVZB, spec(XSensor, param="workMode", uid="work_mode"), spec(XSensor, param="workState", uid="work_state"), spec(XSensor, param="temperature", multiply=0.1), @@ -480,9 +483,23 @@ def spec(cls, base: str = None, enabled: bool = None, **kwargs) -> type: multiply=0.1, uid="eco_target_temperature", ), - spec(XBoolSwitch, param="childLock", uid="child_lock"), - spec(XBoolSwitch, param="windowSwitch", uid="window_switch"), - XSwitch, + spec( + XSensor, + param="tempCorrection", + multiply=0.1, + uid="temperature_correction", + ), + spec(XBoolSwitchTRVZB, param="childLock", uid="child_lock"), + spec(XBoolSwitchTRVZB, param="windowSwitch", uid="window_switch"), + spec(XHexVoltageTRVZB, param="runVoltage", uid="run_voltage"), + spec(XHexVoltageTRVZB, param="limitVoltage", uid="limit_voltage"), + # spec(XSensor, param="mon"), + # spec(XSensor, param="tues"), + # spec(XSensor, param="wed"), + # spec(XSensor, param="thur"), + # spec(XSensor, param="fri"), + # spec(XSensor, param="sat"), + # spec(XSensor, param="sun"), Battery, ZRSSI, ], diff --git a/custom_components/sonoff/sensor.py b/custom_components/sonoff/sensor.py index 80ccd3c..8613324 100644 --- a/custom_components/sonoff/sensor.py +++ b/custom_components/sonoff/sensor.py @@ -361,3 +361,18 @@ def internal_update(self, params: dict = None): if self.hass: self._async_write_ha_state() + + +class XHexVoltageTRVZB(XSensor): + _attr_device_class = SensorDeviceClass.VOLTAGE + _attr_native_unit_of_measurement = UnitOfElectricPotential.VOLT + + def set_state(self, params: dict = None, value: float = None): + try: + value = params[self.param] + value = int(value, 16) * 0.001 + + if value != 0: + XSensor.set_state(self, value=value) + except Exception: + XSensor.set_state(self) diff --git a/custom_components/sonoff/switch.py b/custom_components/sonoff/switch.py index 1717807..8a4a5ab 100644 --- a/custom_components/sonoff/switch.py +++ b/custom_components/sonoff/switch.py @@ -146,3 +146,16 @@ async def async_turn_on(self, *args, **kwargs): async def async_turn_off(self): await self.ewelink.send(self.device, {"switch": False}) + + +class XBoolSwitchTRVZB(XEntity, SwitchEntity): + params = {} + + def set_state(self, params: dict): + self._attr_is_on = params[self.param] + + async def async_turn_on(self, *args, **kwargs): + await self.ewelink.send(self.device, {self.param: True}) + + async def async_turn_off(self): + await self.ewelink.send(self.device, {self.param: False}) \ No newline at end of file