Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New climatisation controls with temperature #574

Merged
merged 4 commits into from
Feb 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 5 additions & 12 deletions custom_components/volkswagencarnet/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""We Connect custom integration for Home Assistant."""

import asyncio
import logging
from datetime import datetime, timedelta
Expand All @@ -20,11 +21,7 @@
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.icon import icon_for_battery_level
from homeassistant.helpers.restore_state import RestoreEntity
from homeassistant.helpers.update_coordinator import (
DataUpdateCoordinator,
UpdateFailed,
CoordinatorEntity,
)
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed, CoordinatorEntity
from volkswagencarnet.vw_connection import Connection
from volkswagencarnet.vw_dashboard import (
Instrument,
Expand Down Expand Up @@ -324,11 +321,7 @@ def async_write_ha_state(self) -> None:
prev: Optional[State] = self.hass.states.get(self.entity_id)

# This is not the best place to handle this, but... :shrug:..
if self.attribute == "requests_remaining" and self.state in [
-1,
STATE_UNAVAILABLE,
STATE_UNKNOWN,
]:
if self.attribute == "requests_remaining" and self.state in [-1, STATE_UNAVAILABLE, STATE_UNKNOWN]:
restored = prev or self.restored_state
if restored is not None:
try:
Expand All @@ -350,6 +343,7 @@ def async_write_ha_state(self) -> None:
prev is None
or str(prev.attributes.get("last_updated", None)) != str(backend_refresh_time)
or str(self.state or STATE_UNKNOWN) != str(prev.state)
or self.component == "climate"
):
super().async_write_ha_state()
else:
Expand Down Expand Up @@ -560,8 +554,7 @@ async def update(self) -> Optional[Vehicle]:
async def report_request(self, vehicle: Vehicle) -> None:
"""Request car to report itself an update to Volkswagen WeConnect."""
report_interval = self.entry.options.get(
CONF_REPORT_SCAN_INTERVAL,
self.entry.data.get(CONF_REPORT_SCAN_INTERVAL, DEFAULT_REPORT_UPDATE_INTERVAL),
CONF_REPORT_SCAN_INTERVAL, self.entry.data.get(CONF_REPORT_SCAN_INTERVAL, DEFAULT_REPORT_UPDATE_INTERVAL)
)

if not self.report_last_updated:
Expand Down
1 change: 1 addition & 0 deletions custom_components/volkswagencarnet/binary_sensor.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""BinarySensor support for Volkswagen We Connect integration."""

import logging
from typing import Union

Expand Down
124 changes: 40 additions & 84 deletions custom_components/volkswagencarnet/climate.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,20 @@
"""Climate support for Volkswagen WeConnect Platform."""

import logging

from homeassistant.components.climate import ClimateEntity
from homeassistant.components.climate.const import (
HVAC_MODE_COOL,
HVAC_MODE_HEAT,
HVAC_MODE_OFF,
SUPPORT_TARGET_TEMPERATURE,
from homeassistant.components.climate import (
ClimateEntity,
ClimateEntityFeature,
HVACMode,
)
from homeassistant.const import (
ATTR_TEMPERATURE,
STATE_UNKNOWN,
TEMP_CELSIUS,
UnitOfTemperature,
)

from . import VolkswagenEntity
from .const import DATA_KEY, DATA, DOMAIN
from . import VolkswagenEntity, VolkswagenData
from .const import DATA_KEY, DATA, DOMAIN, UPDATE_CALLBACK

SUPPORT_HVAC = [HVAC_MODE_COOL, HVAC_MODE_HEAT, HVAC_MODE_OFF]
_LOGGER = logging.getLogger(__name__)


Expand All @@ -39,6 +36,7 @@ async def async_setup_entry(hass, entry, async_add_devices):
vin=coordinator.vin,
component=instrument.component,
attribute=instrument.attr,
callback=hass.data[DOMAIN][entry.entry_id][UPDATE_CALLBACK],
)
for instrument in (instrument for instrument in data.instruments if instrument.component == "climate")
)
Expand All @@ -49,92 +47,50 @@ async def async_setup_entry(hass, entry, async_add_devices):
class VolkswagenClimate(VolkswagenEntity, ClimateEntity):
"""Representation of a Volkswagen WeConnect Climate."""

def set_temperature(self, **kwargs) -> None:
"""Not implemented."""
raise NotImplementedError("Use async_set_temperature instead")

def set_humidity(self, humidity: int) -> None:
"""Not implemented."""
raise NotImplementedError

def set_fan_mode(self, fan_mode: str) -> None:
"""Not implemented."""
raise NotImplementedError

def set_hvac_mode(self, hvac_mode: str) -> None:
"""Not implemented."""
raise NotImplementedError("Use async_set_hvac_mode instead")

def set_swing_mode(self, swing_mode: str) -> None:
"""Not implemented."""
raise NotImplementedError

def set_preset_mode(self, preset_mode: str) -> None:
"""Not implemented."""
raise NotImplementedError

def turn_aux_heat_on(self) -> None:
"""Not implemented."""
raise NotImplementedError

def turn_aux_heat_off(self) -> None:
"""Not implemented."""
raise NotImplementedError

@property
def supported_features(self):
"""Return the list of supported features."""
return SUPPORT_TARGET_TEMPERATURE

@property
def hvac_mode(self):
"""Return hvac operation ie. heat, cool mode.

Need to be one of HVAC_MODE_*.
"""
if not self.instrument.hvac_mode:
return HVAC_MODE_OFF

hvac_modes = {
"HEATING": HVAC_MODE_HEAT,
"COOLING": HVAC_MODE_COOL,
}
return hvac_modes.get(self.instrument.hvac_mode, HVAC_MODE_OFF)

@property
def hvac_modes(self):
"""Return the list of available hvac operation modes.

Need to be a subset of HVAC_MODES.
"""
return SUPPORT_HVAC

@property
def temperature_unit(self):
"""Return the unit of measurement."""
return TEMP_CELSIUS

@property
def target_temperature(self):
"""Return the temperature we try to reach."""
if self.instrument.target_temperature:
return float(self.instrument.target_temperature)
_attr_temperature_unit = UnitOfTemperature.CELSIUS
_attr_target_temperature_step = 0.5
_attr_supported_features = (
ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.TURN_OFF | ClimateEntityFeature.TURN_ON
)
_attr_hvac_modes = [HVACMode.HEAT_COOL, HVACMode.OFF]
_attr_hvac_mode = HVACMode.HEAT_COOL
_attr_min_temp = 15.5
_attr_max_temp = 30
_enable_turn_on_off_backwards_compatibility = False # Remove after HA version 2025.1

def __init__(self, data: VolkswagenData, vin: str, component: str, attribute: str, callback=None):
"""Initialize switch."""
super().__init__(data, vin, component, attribute, callback)
self._update_state()

def _update_state(self) -> None:
self._attr_target_temperature = float(self.instrument.target_temperature)
if self.instrument.hvac_mode is True:
self._attr_hvac_mode = HVACMode.HEAT_COOL
else:
return STATE_UNKNOWN
self._attr_hvac_mode = HVACMode.OFF

async def async_turn_off(self) -> None:
await self.async_set_hvac_mode(HVACMode.OFF)

async def async_turn_on(self) -> None:
await self.async_set_hvac_mode(HVACMode.HEAT_COOL)

async def async_set_temperature(self, **kwargs):
"""Set new target temperatures."""
_LOGGER.debug("Setting temperature for: %s", self.instrument.attr)
temperature = kwargs.get(ATTR_TEMPERATURE)
if temperature:
await self.instrument.set_temperature(temperature)
self._update_state()
self.notify_updated()

async def async_set_hvac_mode(self, hvac_mode):
"""Set new target hvac mode."""
_LOGGER.debug("Setting mode for: %s", self.instrument.attr)
if hvac_mode == HVAC_MODE_OFF:
if hvac_mode == HVACMode.OFF:
await self.instrument.set_hvac_mode(False)
elif hvac_mode == HVAC_MODE_HEAT:
elif hvac_mode == HVACMode.HEAT_COOL:
await self.instrument.set_hvac_mode(True)
self._update_state()
self.notify_updated()
1 change: 1 addition & 0 deletions custom_components/volkswagencarnet/const.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Common constants."""

from datetime import timedelta

DOMAIN = "volkswagencarnet"
Expand Down
1 change: 1 addition & 0 deletions custom_components/volkswagencarnet/device_tracker.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""
Support for Volkswagen WeConnect Platform
"""

import logging

from homeassistant.components.device_tracker import SourceType
Expand Down
1 change: 1 addition & 0 deletions custom_components/volkswagencarnet/lock.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Lock support for Volkswagen WeConnect Platform."""

import logging
from typing import Any

Expand Down
1 change: 1 addition & 0 deletions custom_components/volkswagencarnet/sensor.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Sensor support for Volkswagen We Connect platform."""

import logging
from typing import Union

Expand Down
1 change: 1 addition & 0 deletions custom_components/volkswagencarnet/services.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Services exposed to Home Assistant."""

import logging
from datetime import datetime, timezone

Expand Down
1 change: 1 addition & 0 deletions custom_components/volkswagencarnet/switch.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Support for Volkswagen WeConnect Platform."""

import logging
import re
from datetime import datetime, timezone
Expand Down
1 change: 1 addition & 0 deletions manage/update_manifest.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Update the manifest file."""

import sys
import json
import os
Expand Down
6 changes: 5 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""pytest fixtures."""

from unittest.mock import patch

import pytest
Expand All @@ -13,7 +14,10 @@ def auto_enable_custom_integrations(enable_custom_integrations):
@pytest.fixture
def bypass_setup_fixture():
"""Prevent setup."""
with patch("custom_components.volkswagencarnet.async_setup", return_value=True,), patch(
with patch(
"custom_components.volkswagencarnet.async_setup",
return_value=True,
), patch(
"custom_components.volkswagencarnet.async_setup_entry",
return_value=True,
):
Expand Down
1 change: 1 addition & 0 deletions tests/hass_mocks.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Mock helpers."""

from homeassistant.config_entries import ConfigEntry, SOURCE_USER
from homeassistant.util import uuid as uuid_util

Expand Down
1 change: 1 addition & 0 deletions tests/timer_services_test.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Timer services tests."""

import asyncio
from asyncio import Future
from datetime import timedelta
Expand Down
1 change: 1 addition & 0 deletions tests/util_test.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Timer services tests."""

from asyncio import Future
from unittest.mock import patch, MagicMock

Expand Down
Loading