Skip to content

Commit a9af9ad

Browse files
authored
Merge pull request #232 from loopj/more-thermostats
Add additional thermostat types to the thermostat controller
2 parents a5254ac + 95e8fad commit a9af9ad

12 files changed

+353
-14
lines changed

src/aiovantage/_controllers/port_devices.py

+2
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,6 @@ class PortDevicesController(BaseController[PortDevice]):
1515
"Somfy.RS-485_SDN_2_x2E_0_PORT",
1616
"Somfy.URTSI_2_PORT",
1717
"Vantage.DmxGateway",
18+
"Vantage.Generic_HVAC_RS485_PORT",
19+
"Vantage.HVAC-IU_PORT",
1820
)
+24-8
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,30 @@
1-
from aiovantage.objects import Thermostat
1+
from aiovantage.objects import (
2+
Thermostat,
3+
VantageGenericHVACRS485ZoneChild,
4+
VantageGenericHVACRS485ZoneWithoutFanSpeedChild,
5+
VantageHVACIUZoneChild,
6+
VantageVirtualThermostatPort,
7+
)
28

39
from .base import BaseController
410

11+
ThermostatTypes = (
12+
Thermostat
13+
| VantageGenericHVACRS485ZoneChild
14+
| VantageGenericHVACRS485ZoneWithoutFanSpeedChild
15+
| VantageHVACIUZoneChild
16+
| VantageVirtualThermostatPort
17+
)
18+
"""Types managed by the thermostats controller."""
519

6-
class ThermostatsController(BaseController[Thermostat]):
7-
"""Thermostats controller.
820

9-
Thermostats have a number of temperature sensors associated with them which
10-
represent the current indoor temperature, outdoor temperature, and the
11-
current cool and heat setpoints.
12-
"""
21+
class ThermostatsController(BaseController[ThermostatTypes]):
22+
"""Thermostats controller."""
1323

14-
vantage_types = ("Thermostat",)
24+
vantage_types = (
25+
"Thermostat",
26+
"Vantage.Generic_HVAC_RS485_Zone_CHILD",
27+
"Vantage.Generic_HVAC_RS485_Zone_without_FanSpeed_CHILD",
28+
"Vantage.HVAC-IU-Zone_CHILD",
29+
"Vantage.VirtualThermostat_PORT",
30+
)

src/aiovantage/_object_interfaces/fan.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,15 @@ class FanSpeed(IntEnum):
1414
Off = 0
1515
Low = 1
1616
Medium = 2
17-
Hight = 3
17+
High = 3
1818
Max = 4
1919
Auto = 5
2020

21+
# Properties
22+
speed: FanSpeed | None = None
23+
2124
# Methods
22-
@method("GetSpeed", "GetSpeedHW")
25+
@method("GetSpeed", "GetSpeedHW", property="speed")
2326
async def get_speed(self, *, hw: bool = False) -> FanSpeed:
2427
"""Get the speed of a fan.
2528

src/aiovantage/_object_interfaces/object.py

+40
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
import datetime as dt
2+
from enum import IntEnum
3+
from typing import TypeVar
24

35
from .base import Interface, method
46

7+
IntEnumT = TypeVar("IntEnumT", bound=IntEnum)
8+
59

610
class ObjectInterface(Interface):
711
"""'Object' object interface."""
@@ -181,6 +185,29 @@ async def set_property_ex(self, property: str, value: str) -> None:
181185
# -> R:INVOKE <id> <rcode> Object.SetPropertyEx <property> <value>
182186
await self.invoke("Object.SetPropertyEx", property, value)
183187

188+
@method("IsEnumeratorSupported")
189+
async def is_enumerator_supported(
190+
self, interface_name: str, enumeration_name: str, enumerator_name: str
191+
) -> bool:
192+
"""Check if an enumerator is supported by an object.
193+
194+
Args:
195+
interface_name: The name of the interface to check.
196+
enumeration_name: The name of the enumeration to check.
197+
enumerator_name: The name of the enumerator to check.
198+
199+
Returns:
200+
True if the enumerator is supported, False otherwise.
201+
"""
202+
# INVOKE <id> Object.IsEnumeratorSupported <interfaceName> <enumerationName> <enumeratorName>
203+
# -> R:INVOKE <id> <supported (0/1)> Object.IsEnumeratorSupported <interfaceName> <enumerationName> <enumeratorName>
204+
return await self.invoke(
205+
"Object.IsEnumeratorSupported",
206+
interface_name,
207+
enumeration_name,
208+
enumerator_name,
209+
)
210+
184211
@method("GetMTime")
185212
async def get_m_time(self) -> dt.datetime:
186213
"""Get the modification time of an object.
@@ -213,3 +240,16 @@ async def get_area(self) -> int:
213240
# INVOKE <id> Object.GetArea
214241
# -> R:INVOKE <id> <area> Object.GetArea
215242
return await self.invoke("Object.GetArea")
243+
244+
# Convenience functions, not part of the interface
245+
async def get_supported_enum_values(
246+
self, interface: type[Interface], enum: type[IntEnumT]
247+
) -> list[IntEnumT]:
248+
"""Get all supported enum values of an object."""
249+
return [
250+
val
251+
for val in enum
252+
if await self.is_enumerator_supported(
253+
interface.interface_name, enum.__name__, val.name
254+
)
255+
]

src/aiovantage/_objects/thermostat.py

+9-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""Thermostat object."""
22

3-
from dataclasses import dataclass
3+
from dataclasses import dataclass, field
44

55
from aiovantage.object_interfaces import ThermostatInterface
66

@@ -10,3 +10,11 @@
1010
@dataclass(kw_only=True)
1111
class Thermostat(StationObject, ThermostatInterface):
1212
"""Thermostat object."""
13+
14+
day_mode_event: int = field(default=0, metadata={"name": "DayMode"})
15+
fan_mode_event: int = field(default=0, metadata={"name": "FanMode"})
16+
operation_mode_event: int = field(default=0, metadata={"name": "OperationMode"})
17+
external_temperature: int
18+
display_clock: bool = True
19+
pseudo_mode: bool = True
20+
humidistat: bool = False

src/aiovantage/_objects/types.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
class Parent:
99
"""Vantage parent type."""
1010

11-
id: int
11+
vid: int
1212
position: int = field(metadata={"type": "Attribute"})
1313

1414

src/aiovantage/_objects/vantage_ddg_color_load.py

-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ class ColorType(Enum):
2828
HSL = "HSL"
2929
HSIC = "HSIC"
3030
CCT = "CCT"
31-
COLOR_CHANNEL = "Color Channel"
3231

3332
parent: Parent
3433
color_type: ColorType
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
"""Vantage Generic HVAC RS485 objects."""
2+
3+
from dataclasses import dataclass, field
4+
5+
from aiovantage.object_interfaces import FanInterface, ThermostatInterface
6+
7+
from .child_device import ChildDevice
8+
from .port_device import PortDevice
9+
10+
11+
@dataclass(kw_only=True)
12+
class VantageGenericHVACRS485Port(PortDevice):
13+
"""Vantage Generic HVAC RS485 port device."""
14+
15+
class Meta:
16+
name = "Vantage.Generic_HVAC_RS485_PORT"
17+
18+
@dataclass(kw_only=True)
19+
class FanSpeedSettings:
20+
auto_fan: bool = True
21+
high_fan: bool = True
22+
low_fan: bool = True
23+
max_fan: bool = True
24+
med_fan: bool = True
25+
off_fan: bool = True
26+
27+
@dataclass(kw_only=True)
28+
class SensorSettings:
29+
no_device_sensors: bool = False
30+
outdoor_sensor: int = field(metadata={"name": "outdoorSensor"})
31+
outdoor_temp_offset: str = field(
32+
default="0", metadata={"name": "outdoorTempOffset"}
33+
)
34+
track_sensors: bool = False
35+
36+
@dataclass(kw_only=True)
37+
class SetpointSettings:
38+
bind_setpoints: bool = False
39+
max_temp: int = 25
40+
min_temp: int = 15
41+
42+
fan_boost_option: bool = False
43+
fan_speed_settings: FanSpeedSettings
44+
fan_individual_control: bool = False
45+
receive_port: int
46+
sensor_settings: SensorSettings
47+
setpoint_settings: SetpointSettings
48+
49+
50+
@dataclass(kw_only=True)
51+
class VantageGenericHVACRS485TechContactsChild(ChildDevice):
52+
"""Vantage Generic HVAC RS485 tech contacts child device."""
53+
54+
class Meta:
55+
name = "Vantage.Generic_HVAC_RS485_TechContacts_CHILD"
56+
57+
58+
@dataclass(kw_only=True)
59+
class VantageGenericHVACRS485CompoundChild(ChildDevice, ThermostatInterface):
60+
"""Vantage Generic HVAC RS485 compound child device."""
61+
62+
class Meta:
63+
name = "Vantage.Generic_HVAC_RS485_Compound_CHILD"
64+
65+
adress_number: int = 1 # NOTE: Intentional typo to match the underlying object
66+
67+
68+
@dataclass(kw_only=True)
69+
class VantageGenericHVACRS485ZoneChild(ChildDevice, ThermostatInterface, FanInterface):
70+
"""Vantage Generic HVAC RS485 zone child device."""
71+
72+
class Meta:
73+
name = "Vantage.Generic_HVAC_RS485_Zone_CHILD"
74+
75+
@dataclass(kw_only=True)
76+
class IndoorSettings:
77+
indoor_sensor: int = field(metadata={"name": "indoorSensor"})
78+
indoor_temp_offset: str = "0"
79+
80+
indoor_settings: IndoorSettings
81+
position_number: int = 1
82+
83+
84+
@dataclass(kw_only=True)
85+
class VantageGenericHVACRS485ZoneWithoutFanSpeedChild(
86+
ChildDevice, ThermostatInterface, FanInterface
87+
):
88+
"""Vantage Generic HVAC RS485 zone child device without fan speed."""
89+
90+
class Meta:
91+
name = "Vantage.Generic_HVAC_RS485_Zone_without_FanSpeed_CHILD"
92+
93+
@dataclass(kw_only=True)
94+
class IndoorSettings:
95+
indoor_sensor: int = field(metadata={"name": "indoorSensor"})
96+
indoor_temp_offset: str = "0"
97+
98+
indoor_settings: IndoorSettings
99+
position_number: int = 1
+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
"""Vantage HVAC-IU objects."""
2+
3+
from dataclasses import dataclass, field
4+
5+
from aiovantage.object_interfaces import FanInterface, ThermostatInterface
6+
7+
from .child_device import ChildDevice
8+
from .port_device import PortDevice
9+
10+
11+
@dataclass(kw_only=True)
12+
class VantageHVACIUPort(PortDevice):
13+
"""Vantage HVAC-IU port device."""
14+
15+
class Meta:
16+
name = "Vantage.HVAC-IU_PORT"
17+
18+
temperature_format: str = field(
19+
default="Celcius", # NOTE: Intentional typo to match the underlying object
20+
metadata={"name": "aTemperatureFormat"},
21+
)
22+
outdoor_sensor: int
23+
pauze_time: int = 1 # NOTE: Intentional typo to match the underlying object
24+
serial_number: str = "0"
25+
26+
27+
@dataclass(kw_only=True)
28+
class VantageHVACIULineChild(ChildDevice):
29+
"""Vantage HVAC-IU line child device."""
30+
31+
class Meta:
32+
name = "Vantage.HVAC-IU-Line_CHILD"
33+
34+
@dataclass(kw_only=True)
35+
class OperationModes:
36+
auto: bool = True
37+
cool: bool = True
38+
heat: bool = True
39+
40+
@dataclass(kw_only=True)
41+
class FanSpeeds:
42+
auto: bool = True
43+
high: bool = True
44+
low: bool = True
45+
max: bool = True
46+
med: bool = True
47+
48+
device_type: str = "Daikin"
49+
line_number: int = 1
50+
operation_modes: OperationModes
51+
fan_speeds: FanSpeeds = field(metadata={"name": "xFanSpeeds"})
52+
53+
54+
@dataclass(kw_only=True)
55+
class VantageHVACIUZoneChild(ChildDevice, ThermostatInterface, FanInterface):
56+
"""Vantage HVAC-IU zone child device."""
57+
58+
class Meta:
59+
name = "Vantage.HVAC-IU-Zone_CHILD"
60+
61+
@dataclass(kw_only=True)
62+
class IndoorSensor:
63+
indoor_sensor: int
64+
indoor_temp_offset: str = "0"
65+
66+
main_zone: str = field(
67+
metadata={"name": "ZoneNumber", "wrapper": "aMainZonePlaceHolder"}
68+
)
69+
70+
grouped_zones: list[str] = field(
71+
default_factory=list,
72+
metadata={"name": "ZoneNumberChild", "wrapper": "bGroupZonePlaceHolder"},
73+
)
74+
75+
indoor_sensors: list[IndoorSensor] = field(
76+
default_factory=list, metadata={"name": "cIndoorSensors"}
77+
)

0 commit comments

Comments
 (0)