Add support for HeatIt Thermostat TF056 to homee (#145515)

* adapt climate for Heatit TF 056

* add sensors & numbers for Heatit TF056

* Add select for Heatit TF056

* Adapt climat tests for changes

* Fix sentence case

* fix review comments

* Update homeassistant/components/homee/climate.py

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>

* fix tests

* update diagnostics snapshot for this change

---------

Co-authored-by: Franck Nijhof <frenck@frenck.nl>
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
Co-authored-by: Franck Nijhof <git@frenck.dev>
This commit is contained in:
Markus Adrario 2025-06-10 11:41:13 -06:00 committed by GitHub
parent 481bf2694b
commit 7a428a66bd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 1402 additions and 9 deletions

View File

@ -83,7 +83,7 @@ class HomeeClimate(HomeeNodeEntity, ClimateEntity):
if ClimateEntityFeature.TURN_OFF in self.supported_features and ( if ClimateEntityFeature.TURN_OFF in self.supported_features and (
self._heating_mode is not None self._heating_mode is not None
): ):
if self._heating_mode.current_value == 0: if self._heating_mode.current_value == self._heating_mode.minimum:
return HVACMode.OFF return HVACMode.OFF
return HVACMode.HEAT return HVACMode.HEAT
@ -91,7 +91,10 @@ class HomeeClimate(HomeeNodeEntity, ClimateEntity):
@property @property
def hvac_action(self) -> HVACAction: def hvac_action(self) -> HVACAction:
"""Return the hvac action.""" """Return the hvac action."""
if self._heating_mode is not None and self._heating_mode.current_value == 0: if (
self._heating_mode is not None
and self._heating_mode.current_value == self._heating_mode.minimum
):
return HVACAction.OFF return HVACAction.OFF
if ( if (
@ -110,10 +113,12 @@ class HomeeClimate(HomeeNodeEntity, ClimateEntity):
if ( if (
ClimateEntityFeature.PRESET_MODE in self.supported_features ClimateEntityFeature.PRESET_MODE in self.supported_features
and self._heating_mode is not None and self._heating_mode is not None
and self._heating_mode.current_value > 0 and self._heating_mode.current_value > self._heating_mode.minimum
): ):
assert self._attr_preset_modes is not None assert self._attr_preset_modes is not None
return self._attr_preset_modes[int(self._heating_mode.current_value) - 1] return self._attr_preset_modes[
int(self._heating_mode.current_value - self._heating_mode.minimum) - 1
]
return PRESET_NONE return PRESET_NONE
@ -147,14 +152,16 @@ class HomeeClimate(HomeeNodeEntity, ClimateEntity):
# Currently only HEAT and OFF are supported. # Currently only HEAT and OFF are supported.
assert self._heating_mode is not None assert self._heating_mode is not None
await self.async_set_homee_value( await self.async_set_homee_value(
self._heating_mode, float(hvac_mode == HVACMode.HEAT) self._heating_mode,
(hvac_mode == HVACMode.HEAT) + self._heating_mode.minimum,
) )
async def async_set_preset_mode(self, preset_mode: str) -> None: async def async_set_preset_mode(self, preset_mode: str) -> None:
"""Set new target preset mode.""" """Set new target preset mode."""
assert self._heating_mode is not None and self._attr_preset_modes is not None assert self._heating_mode is not None and self._attr_preset_modes is not None
await self.async_set_homee_value( await self.async_set_homee_value(
self._heating_mode, self._attr_preset_modes.index(preset_mode) + 1 self._heating_mode,
self._attr_preset_modes.index(preset_mode) + self._heating_mode.minimum + 1,
) )
async def async_set_temperature(self, **kwargs: Any) -> None: async def async_set_temperature(self, **kwargs: Any) -> None:
@ -168,12 +175,16 @@ class HomeeClimate(HomeeNodeEntity, ClimateEntity):
async def async_turn_on(self) -> None: async def async_turn_on(self) -> None:
"""Turn the entity on.""" """Turn the entity on."""
assert self._heating_mode is not None assert self._heating_mode is not None
await self.async_set_homee_value(self._heating_mode, 1) await self.async_set_homee_value(
self._heating_mode, 1 + self._heating_mode.minimum
)
async def async_turn_off(self) -> None: async def async_turn_off(self) -> None:
"""Turn the entity on.""" """Turn the entity on."""
assert self._heating_mode is not None assert self._heating_mode is not None
await self.async_set_homee_value(self._heating_mode, 0) await self.async_set_homee_value(
self._heating_mode, 0 + self._heating_mode.minimum
)
def get_climate_features( def get_climate_features(
@ -193,7 +204,10 @@ def get_climate_features(
if attribute.maximum > 1: if attribute.maximum > 1:
# Node supports more modes than off and heating. # Node supports more modes than off and heating.
features |= ClimateEntityFeature.PRESET_MODE features |= ClimateEntityFeature.PRESET_MODE
preset_modes.extend([PRESET_ECO, PRESET_BOOST, PRESET_MANUAL]) if attribute.maximum < 5:
preset_modes.extend([PRESET_ECO, PRESET_BOOST, PRESET_MANUAL])
else:
preset_modes.extend([PRESET_ECO])
if len(preset_modes) > 0: if len(preset_modes) > 0:
preset_modes.insert(0, PRESET_NONE) preset_modes.insert(0, PRESET_NONE)

View File

@ -31,6 +31,22 @@ class HomeeNumberEntityDescription(NumberEntityDescription):
NUMBER_DESCRIPTIONS = { NUMBER_DESCRIPTIONS = {
AttributeType.BUTTON_BRIGHTNESS_ACTIVE: HomeeNumberEntityDescription(
key="button_brightness_active",
entity_category=EntityCategory.CONFIG,
),
AttributeType.BUTTON_BRIGHTNESS_DIMMED: HomeeNumberEntityDescription(
key="button_brightness_dimmed",
entity_category=EntityCategory.CONFIG,
),
AttributeType.DISPLAY_BRIGHTNESS_ACTIVE: HomeeNumberEntityDescription(
key="display_brightness_active",
entity_category=EntityCategory.CONFIG,
),
AttributeType.DISPLAY_BRIGHTNESS_DIMMED: HomeeNumberEntityDescription(
key="display_brightness_dimmed",
entity_category=EntityCategory.CONFIG,
),
AttributeType.DOWN_POSITION: HomeeNumberEntityDescription( AttributeType.DOWN_POSITION: HomeeNumberEntityDescription(
key="down_position", key="down_position",
entity_category=EntityCategory.CONFIG, entity_category=EntityCategory.CONFIG,
@ -48,6 +64,14 @@ NUMBER_DESCRIPTIONS = {
key="endposition_configuration", key="endposition_configuration",
entity_category=EntityCategory.CONFIG, entity_category=EntityCategory.CONFIG,
), ),
AttributeType.EXTERNAL_TEMPERATURE_OFFSET: HomeeNumberEntityDescription(
key="external_temperature_offset",
entity_category=EntityCategory.CONFIG,
),
AttributeType.FLOOR_TEMPERATURE_OFFSET: HomeeNumberEntityDescription(
key="floor_temperature_offset",
entity_category=EntityCategory.CONFIG,
),
AttributeType.MOTION_ALARM_CANCELATION_DELAY: HomeeNumberEntityDescription( AttributeType.MOTION_ALARM_CANCELATION_DELAY: HomeeNumberEntityDescription(
key="motion_alarm_cancelation_delay", key="motion_alarm_cancelation_delay",
device_class=NumberDeviceClass.DURATION, device_class=NumberDeviceClass.DURATION,
@ -83,6 +107,11 @@ NUMBER_DESCRIPTIONS = {
key="temperature_offset", key="temperature_offset",
entity_category=EntityCategory.CONFIG, entity_category=EntityCategory.CONFIG,
), ),
AttributeType.TEMPERATURE_REPORT_INTERVAL: HomeeNumberEntityDescription(
key="temperature_report_interval",
device_class=NumberDeviceClass.DURATION,
entity_category=EntityCategory.CONFIG,
),
AttributeType.UP_TIME: HomeeNumberEntityDescription( AttributeType.UP_TIME: HomeeNumberEntityDescription(
key="up_time", key="up_time",
device_class=NumberDeviceClass.DURATION, device_class=NumberDeviceClass.DURATION,

View File

@ -14,6 +14,11 @@ from .entity import HomeeEntity
PARALLEL_UPDATES = 0 PARALLEL_UPDATES = 0
SELECT_DESCRIPTIONS: dict[AttributeType, SelectEntityDescription] = { SELECT_DESCRIPTIONS: dict[AttributeType, SelectEntityDescription] = {
AttributeType.DISPLAY_TEMPERATURE_SELECTION: SelectEntityDescription(
key="display_temperature_selection",
options=["target", "current"],
entity_category=EntityCategory.CONFIG,
),
AttributeType.REPEATER_MODE: SelectEntityDescription( AttributeType.REPEATER_MODE: SelectEntityDescription(
key="repeater_mode", key="repeater_mode",
options=["off", "level1", "level2"], options=["off", "level1", "level2"],

View File

@ -129,6 +129,16 @@ SENSOR_DESCRIPTIONS: dict[AttributeType, HomeeSensorEntityDescription] = {
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
entity_category=EntityCategory.DIAGNOSTIC, entity_category=EntityCategory.DIAGNOSTIC,
), ),
AttributeType.EXTERNAL_TEMPERATURE: HomeeSensorEntityDescription(
key="external_temperature",
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
),
AttributeType.FLOOR_TEMPERATURE: HomeeSensorEntityDescription(
key="floor_temperature",
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
),
AttributeType.INDOOR_RELATIVE_HUMIDITY: HomeeSensorEntityDescription( AttributeType.INDOOR_RELATIVE_HUMIDITY: HomeeSensorEntityDescription(
key="indoor_humidity", key="indoor_humidity",
device_class=SensorDeviceClass.HUMIDITY, device_class=SensorDeviceClass.HUMIDITY,

View File

@ -199,6 +199,18 @@
} }
}, },
"number": { "number": {
"button_brightness_active": {
"name": "Button brightness (active)"
},
"button_brightness_dimmed": {
"name": "Button brightness (dimmed)"
},
"display_brightness_active": {
"name": "Display brightness (active)"
},
"display_brightness_dimmed": {
"name": "Display brightness (dimmed)"
},
"down_position": { "down_position": {
"name": "Down position" "name": "Down position"
}, },
@ -211,6 +223,12 @@
"endposition_configuration": { "endposition_configuration": {
"name": "End position" "name": "End position"
}, },
"external_temperature_offset": {
"name": "External temperature offset"
},
"floor_temperature_offset": {
"name": "Floor temperature offset"
},
"motion_alarm_cancelation_delay": { "motion_alarm_cancelation_delay": {
"name": "Motion alarm delay" "name": "Motion alarm delay"
}, },
@ -235,6 +253,9 @@
"temperature_offset": { "temperature_offset": {
"name": "Temperature offset" "name": "Temperature offset"
}, },
"temperature_report_interval": {
"name": "Temperature report interval"
},
"up_time": { "up_time": {
"name": "Up-movement duration" "name": "Up-movement duration"
}, },
@ -246,6 +267,13 @@
} }
}, },
"select": { "select": {
"display_temperature_selection": {
"name": "Displayed temperature",
"state": {
"target": "Target",
"current": "Measured"
}
},
"repeater_mode": { "repeater_mode": {
"name": "Repeater mode", "name": "Repeater mode",
"state": { "state": {
@ -277,6 +305,12 @@
"exhaust_motor_revs": { "exhaust_motor_revs": {
"name": "Exhaust motor speed" "name": "Exhaust motor speed"
}, },
"external_temperature": {
"name": "External temperature"
},
"floor_temperature": {
"name": "Floor temperature"
},
"indoor_humidity": { "indoor_humidity": {
"name": "Indoor humidity" "name": "Indoor humidity"
}, },

View File

@ -353,6 +353,153 @@
"based_on": 1, "based_on": 1,
"data": "", "data": "",
"name": "" "name": ""
},
{
"id": 17,
"node_id": 1,
"instance": 0,
"minimum": -6,
"maximum": 6,
"current_value": 0.0,
"target_value": 0.0,
"last_value": 0.0,
"unit": "°C",
"step_value": 0.1,
"editable": 1,
"type": 340,
"state": 1,
"last_changed": 1624806665,
"changed_by": 1,
"changed_by_id": 0,
"based_on": 1,
"data": "",
"name": ""
},
{
"id": 18,
"node_id": 1,
"instance": 0,
"minimum": -6,
"maximum": 6,
"current_value": 0.0,
"target_value": 0.0,
"last_value": 0.0,
"unit": "°C",
"step_value": 0.1,
"editable": 1,
"type": 341,
"state": 1,
"last_changed": 1624806728,
"changed_by": 1,
"changed_by_id": 0,
"based_on": 1,
"data": "",
"name": ""
},
{
"id": 19,
"node_id": 1,
"instance": 0,
"minimum": 0,
"maximum": 32767,
"current_value": 60.0,
"target_value": 60.0,
"last_value": 60.0,
"unit": "s",
"step_value": 1.0,
"editable": 1,
"type": 59,
"state": 1,
"last_changed": 1624806729,
"changed_by": 1,
"changed_by_id": 0,
"based_on": 1,
"data": "",
"name": ""
},
{
"id": 20,
"node_id": 1,
"instance": 0,
"minimum": 0,
"maximum": 100,
"current_value": 78.0,
"target_value": 78.0,
"last_value": 78.0,
"unit": "%",
"step_value": 1.0,
"editable": 1,
"type": 342,
"state": 1,
"last_changed": 1624806729,
"changed_by": 1,
"changed_by_id": 0,
"based_on": 1,
"data": "",
"name": ""
},
{
"id": 21,
"node_id": 1,
"instance": 0,
"minimum": 0,
"maximum": 100,
"current_value": 23.0,
"target_value": 23.0,
"last_value": 23.0,
"unit": "%",
"step_value": 1.0,
"editable": 1,
"type": 343,
"state": 1,
"last_changed": 1624806729,
"changed_by": 1,
"changed_by_id": 0,
"based_on": 1,
"data": "",
"name": ""
},
{
"id": 22,
"node_id": 1,
"instance": 0,
"minimum": 0,
"maximum": 100,
"current_value": 100.0,
"target_value": 100.0,
"last_value": 100.0,
"unit": "%",
"step_value": 1.0,
"editable": 1,
"type": 344,
"state": 1,
"last_changed": 1624806728,
"changed_by": 1,
"changed_by_id": 0,
"based_on": 1,
"data": "",
"name": ""
},
{
"id": 23,
"node_id": 1,
"instance": 0,
"minimum": 0,
"maximum": 100,
"current_value": 23.0,
"target_value": 23.0,
"last_value": 50.0,
"unit": "%",
"step_value": 1.0,
"editable": 1,
"type": 345,
"state": 1,
"last_changed": 1624806728,
"changed_by": 1,
"changed_by_id": 0,
"based_on": 1,
"data": "",
"name": ""
} }
] ]
} }

View File

@ -38,6 +38,27 @@
"based_on": 1, "based_on": 1,
"data": "", "data": "",
"name": "" "name": ""
},
{
"id": 2,
"node_id": 1,
"instance": 0,
"minimum": 0,
"maximum": 1,
"current_value": 0.0,
"target_value": 0.0,
"last_value": 0.0,
"unit": "",
"step_value": 1.0,
"editable": 1,
"type": 346,
"state": 1,
"last_changed": 1624806728,
"changed_by": 1,
"changed_by_id": 0,
"based_on": 1,
"data": "",
"name": ""
} }
] ]
} }

View File

@ -731,6 +731,62 @@
"based_on": 1, "based_on": 1,
"data": "", "data": "",
"name": "" "name": ""
},
{
"id": 34,
"node_id": 1,
"instance": 0,
"minimum": -50,
"maximum": 125,
"current_value": 23.6,
"target_value": 23.6,
"last_value": 23.6,
"unit": "°C",
"step_value": 1.0,
"editable": 0,
"type": 315,
"state": 1,
"last_changed": 1747078279,
"changed_by": 1,
"changed_by_id": 0,
"based_on": 1,
"data": "",
"name": "",
"options": {
"history": {
"day": 1,
"week": 26,
"month": 6
}
}
},
{
"id": 35,
"node_id": 1,
"instance": 0,
"minimum": -50,
"maximum": 125,
"current_value": 0.0,
"target_value": 0.0,
"last_value": 0.0,
"unit": "°C",
"step_value": 1.0,
"editable": 0,
"type": 316,
"state": 1,
"last_changed": 1747078280,
"changed_by": 1,
"changed_by_id": 0,
"based_on": 1,
"data": "",
"name": "",
"options": {
"history": {
"day": 1,
"week": 26,
"month": 6
}
}
} }
] ]
} }

View File

@ -0,0 +1,102 @@
{
"id": 5,
"name": "Test Thermostat 5",
"profile": 3033,
"image": "default",
"favorite": 0,
"order": 32,
"protocol": 1,
"routing": 0,
"state": 1,
"state_changed": 1712840187,
"added": 1655274291,
"history": 1,
"cube_type": 1,
"note": "",
"services": 7,
"phonetic_name": "",
"owner": 2,
"security": 0,
"attributes": [
{
"id": 1,
"node_id": 5,
"instance": 0,
"minimum": 10,
"maximum": 32,
"current_value": 12.0,
"target_value": 13.0,
"last_value": 12.0,
"unit": "°C",
"step_value": 0.5,
"editable": 1,
"type": 6,
"state": 2,
"last_changed": 1713695529,
"changed_by": 3,
"changed_by_id": 0,
"based_on": 1,
"data": "",
"name": "",
"options": {
"automations": ["step"],
"history": {
"day": 35,
"week": 5,
"month": 1,
"stepped": true
}
}
},
{
"id": 2,
"node_id": 5,
"instance": 0,
"minimum": -50,
"maximum": 125,
"current_value": 19.55,
"target_value": 19.55,
"last_value": 21.07,
"unit": "°C",
"step_value": 0.1,
"editable": 0,
"type": 5,
"state": 1,
"last_changed": 1713695528,
"changed_by": 1,
"changed_by_id": 0,
"based_on": 1,
"data": "",
"name": "",
"options": {
"observed_by": [240],
"history": {
"day": 1,
"week": 26,
"month": 6
}
}
},
{
"id": 3,
"node_id": 5,
"instance": 0,
"minimum": 10,
"maximum": 12,
"current_value": 11.0,
"target_value": 11.0,
"last_value": 11.0,
"unit": "",
"step_value": 1.0,
"editable": 1,
"type": 258,
"state": 1,
"last_changed": 1746379402,
"changed_by": 1,
"changed_by_id": 0,
"based_on": 1,
"data": "",
"name": ""
}
]
}

View File

@ -276,3 +276,79 @@
'state': 'heat', 'state': 'heat',
}) })
# --- # ---
# name: test_climate_snapshot[climate.test_thermostat_5-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'hvac_modes': list([
<HVACMode.HEAT: 'heat'>,
<HVACMode.OFF: 'off'>,
]),
'max_temp': 32,
'min_temp': 10,
'preset_modes': list([
'none',
'eco',
]),
'target_temp_step': 0.5,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'climate',
'entity_category': None,
'entity_id': 'climate.test_thermostat_5',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': None,
'platform': 'homee',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': <ClimateEntityFeature: 401>,
'translation_key': 'homee',
'unique_id': '00055511EECC-5-1',
'unit_of_measurement': None,
})
# ---
# name: test_climate_snapshot[climate.test_thermostat_5-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'current_temperature': 19.6,
'friendly_name': 'Test Thermostat 5',
'hvac_action': <HVACAction.IDLE: 'idle'>,
'hvac_modes': list([
<HVACMode.HEAT: 'heat'>,
<HVACMode.OFF: 'off'>,
]),
'max_temp': 32,
'min_temp': 10,
'preset_mode': 'none',
'preset_modes': list([
'none',
'eco',
]),
'supported_features': <ClimateEntityFeature: 401>,
'target_temp_step': 0.5,
'temperature': 12.0,
}),
'context': <ANY>,
'entity_id': 'climate.test_thermostat_5',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'heat',
})
# ---

View File

@ -342,6 +342,153 @@
'type': 338, 'type': 338,
'unit': 'n/a', 'unit': 'n/a',
}), }),
dict({
'based_on': 1,
'changed_by': 1,
'changed_by_id': 0,
'current_value': 0.0,
'data': '',
'editable': 1,
'id': 17,
'instance': 0,
'last_changed': 1624806665,
'last_value': 0.0,
'maximum': 6,
'minimum': -6,
'name': '',
'node_id': 1,
'state': 1,
'step_value': 0.1,
'target_value': 0.0,
'type': 340,
'unit': '°C',
}),
dict({
'based_on': 1,
'changed_by': 1,
'changed_by_id': 0,
'current_value': 0.0,
'data': '',
'editable': 1,
'id': 18,
'instance': 0,
'last_changed': 1624806728,
'last_value': 0.0,
'maximum': 6,
'minimum': -6,
'name': '',
'node_id': 1,
'state': 1,
'step_value': 0.1,
'target_value': 0.0,
'type': 341,
'unit': '°C',
}),
dict({
'based_on': 1,
'changed_by': 1,
'changed_by_id': 0,
'current_value': 60.0,
'data': '',
'editable': 1,
'id': 19,
'instance': 0,
'last_changed': 1624806729,
'last_value': 60.0,
'maximum': 32767,
'minimum': 0,
'name': '',
'node_id': 1,
'state': 1,
'step_value': 1.0,
'target_value': 60.0,
'type': 59,
'unit': 's',
}),
dict({
'based_on': 1,
'changed_by': 1,
'changed_by_id': 0,
'current_value': 78.0,
'data': '',
'editable': 1,
'id': 20,
'instance': 0,
'last_changed': 1624806729,
'last_value': 78.0,
'maximum': 100,
'minimum': 0,
'name': '',
'node_id': 1,
'state': 1,
'step_value': 1.0,
'target_value': 78.0,
'type': 342,
'unit': '%',
}),
dict({
'based_on': 1,
'changed_by': 1,
'changed_by_id': 0,
'current_value': 23.0,
'data': '',
'editable': 1,
'id': 21,
'instance': 0,
'last_changed': 1624806729,
'last_value': 23.0,
'maximum': 100,
'minimum': 0,
'name': '',
'node_id': 1,
'state': 1,
'step_value': 1.0,
'target_value': 23.0,
'type': 343,
'unit': '%',
}),
dict({
'based_on': 1,
'changed_by': 1,
'changed_by_id': 0,
'current_value': 100.0,
'data': '',
'editable': 1,
'id': 22,
'instance': 0,
'last_changed': 1624806728,
'last_value': 100.0,
'maximum': 100,
'minimum': 0,
'name': '',
'node_id': 1,
'state': 1,
'step_value': 1.0,
'target_value': 100.0,
'type': 344,
'unit': '%',
}),
dict({
'based_on': 1,
'changed_by': 1,
'changed_by_id': 0,
'current_value': 23.0,
'data': '',
'editable': 1,
'id': 23,
'instance': 0,
'last_changed': 1624806728,
'last_value': 50.0,
'maximum': 100,
'minimum': 0,
'name': '',
'node_id': 1,
'state': 1,
'step_value': 1.0,
'target_value': 23.0,
'type': 345,
'unit': '%',
}),
]), ]),
'cube_type': 3, 'cube_type': 3,
'favorite': 0, 'favorite': 0,
@ -920,6 +1067,153 @@
'type': 338, 'type': 338,
'unit': 'n/a', 'unit': 'n/a',
}), }),
dict({
'based_on': 1,
'changed_by': 1,
'changed_by_id': 0,
'current_value': 0.0,
'data': '',
'editable': 1,
'id': 17,
'instance': 0,
'last_changed': 1624806665,
'last_value': 0.0,
'maximum': 6,
'minimum': -6,
'name': '',
'node_id': 1,
'state': 1,
'step_value': 0.1,
'target_value': 0.0,
'type': 340,
'unit': '°C',
}),
dict({
'based_on': 1,
'changed_by': 1,
'changed_by_id': 0,
'current_value': 0.0,
'data': '',
'editable': 1,
'id': 18,
'instance': 0,
'last_changed': 1624806728,
'last_value': 0.0,
'maximum': 6,
'minimum': -6,
'name': '',
'node_id': 1,
'state': 1,
'step_value': 0.1,
'target_value': 0.0,
'type': 341,
'unit': '°C',
}),
dict({
'based_on': 1,
'changed_by': 1,
'changed_by_id': 0,
'current_value': 60.0,
'data': '',
'editable': 1,
'id': 19,
'instance': 0,
'last_changed': 1624806729,
'last_value': 60.0,
'maximum': 32767,
'minimum': 0,
'name': '',
'node_id': 1,
'state': 1,
'step_value': 1.0,
'target_value': 60.0,
'type': 59,
'unit': 's',
}),
dict({
'based_on': 1,
'changed_by': 1,
'changed_by_id': 0,
'current_value': 78.0,
'data': '',
'editable': 1,
'id': 20,
'instance': 0,
'last_changed': 1624806729,
'last_value': 78.0,
'maximum': 100,
'minimum': 0,
'name': '',
'node_id': 1,
'state': 1,
'step_value': 1.0,
'target_value': 78.0,
'type': 342,
'unit': '%',
}),
dict({
'based_on': 1,
'changed_by': 1,
'changed_by_id': 0,
'current_value': 23.0,
'data': '',
'editable': 1,
'id': 21,
'instance': 0,
'last_changed': 1624806729,
'last_value': 23.0,
'maximum': 100,
'minimum': 0,
'name': '',
'node_id': 1,
'state': 1,
'step_value': 1.0,
'target_value': 23.0,
'type': 343,
'unit': '%',
}),
dict({
'based_on': 1,
'changed_by': 1,
'changed_by_id': 0,
'current_value': 100.0,
'data': '',
'editable': 1,
'id': 22,
'instance': 0,
'last_changed': 1624806728,
'last_value': 100.0,
'maximum': 100,
'minimum': 0,
'name': '',
'node_id': 1,
'state': 1,
'step_value': 1.0,
'target_value': 100.0,
'type': 344,
'unit': '%',
}),
dict({
'based_on': 1,
'changed_by': 1,
'changed_by_id': 0,
'current_value': 23.0,
'data': '',
'editable': 1,
'id': 23,
'instance': 0,
'last_changed': 1624806728,
'last_value': 50.0,
'maximum': 100,
'minimum': 0,
'name': '',
'node_id': 1,
'state': 1,
'step_value': 1.0,
'target_value': 23.0,
'type': 345,
'unit': '%',
}),
]), ]),
'cube_type': 3, 'cube_type': 3,
'favorite': 0, 'favorite': 0,

View File

@ -1,4 +1,236 @@
# serializer version: 1 # serializer version: 1
# name: test_number_snapshot[number.test_number_button_brightness_active-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'max': 100,
'min': 0,
'mode': <NumberMode.AUTO: 'auto'>,
'step': 1.0,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'number',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'number.test_number_button_brightness_active',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Button brightness (active)',
'platform': 'homee',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'button_brightness_active',
'unique_id': '00055511EECC-1-22',
'unit_of_measurement': '%',
})
# ---
# name: test_number_snapshot[number.test_number_button_brightness_active-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Test Number Button brightness (active)',
'max': 100,
'min': 0,
'mode': <NumberMode.AUTO: 'auto'>,
'step': 1.0,
'unit_of_measurement': '%',
}),
'context': <ANY>,
'entity_id': 'number.test_number_button_brightness_active',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '100.0',
})
# ---
# name: test_number_snapshot[number.test_number_button_brightness_dimmed-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'max': 100,
'min': 0,
'mode': <NumberMode.AUTO: 'auto'>,
'step': 1.0,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'number',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'number.test_number_button_brightness_dimmed',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Button brightness (dimmed)',
'platform': 'homee',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'button_brightness_dimmed',
'unique_id': '00055511EECC-1-23',
'unit_of_measurement': '%',
})
# ---
# name: test_number_snapshot[number.test_number_button_brightness_dimmed-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Test Number Button brightness (dimmed)',
'max': 100,
'min': 0,
'mode': <NumberMode.AUTO: 'auto'>,
'step': 1.0,
'unit_of_measurement': '%',
}),
'context': <ANY>,
'entity_id': 'number.test_number_button_brightness_dimmed',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '23.0',
})
# ---
# name: test_number_snapshot[number.test_number_display_brightness_active-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'max': 100,
'min': 0,
'mode': <NumberMode.AUTO: 'auto'>,
'step': 1.0,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'number',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'number.test_number_display_brightness_active',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Display brightness (active)',
'platform': 'homee',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'display_brightness_active',
'unique_id': '00055511EECC-1-20',
'unit_of_measurement': '%',
})
# ---
# name: test_number_snapshot[number.test_number_display_brightness_active-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Test Number Display brightness (active)',
'max': 100,
'min': 0,
'mode': <NumberMode.AUTO: 'auto'>,
'step': 1.0,
'unit_of_measurement': '%',
}),
'context': <ANY>,
'entity_id': 'number.test_number_display_brightness_active',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '78.0',
})
# ---
# name: test_number_snapshot[number.test_number_display_brightness_dimmed-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'max': 100,
'min': 0,
'mode': <NumberMode.AUTO: 'auto'>,
'step': 1.0,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'number',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'number.test_number_display_brightness_dimmed',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Display brightness (dimmed)',
'platform': 'homee',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'display_brightness_dimmed',
'unique_id': '00055511EECC-1-21',
'unit_of_measurement': '%',
})
# ---
# name: test_number_snapshot[number.test_number_display_brightness_dimmed-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Test Number Display brightness (dimmed)',
'max': 100,
'min': 0,
'mode': <NumberMode.AUTO: 'auto'>,
'step': 1.0,
'unit_of_measurement': '%',
}),
'context': <ANY>,
'entity_id': 'number.test_number_display_brightness_dimmed',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '23.0',
})
# ---
# name: test_number_snapshot[number.test_number_down_movement_duration-entry] # name: test_number_snapshot[number.test_number_down_movement_duration-entry]
EntityRegistryEntrySnapshot({ EntityRegistryEntrySnapshot({
'aliases': set({ 'aliases': set({
@ -231,6 +463,122 @@
'state': '129.0', 'state': '129.0',
}) })
# --- # ---
# name: test_number_snapshot[number.test_number_external_temperature_offset-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'max': 6,
'min': -6,
'mode': <NumberMode.AUTO: 'auto'>,
'step': 0.1,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'number',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'number.test_number_external_temperature_offset',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'External temperature offset',
'platform': 'homee',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'external_temperature_offset',
'unique_id': '00055511EECC-1-17',
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
})
# ---
# name: test_number_snapshot[number.test_number_external_temperature_offset-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Test Number External temperature offset',
'max': 6,
'min': -6,
'mode': <NumberMode.AUTO: 'auto'>,
'step': 0.1,
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
}),
'context': <ANY>,
'entity_id': 'number.test_number_external_temperature_offset',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '0.0',
})
# ---
# name: test_number_snapshot[number.test_number_floor_temperature_offset-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'max': 6,
'min': -6,
'mode': <NumberMode.AUTO: 'auto'>,
'step': 0.1,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'number',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'number.test_number_floor_temperature_offset',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Floor temperature offset',
'platform': 'homee',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'floor_temperature_offset',
'unique_id': '00055511EECC-1-18',
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
})
# ---
# name: test_number_snapshot[number.test_number_floor_temperature_offset-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Test Number Floor temperature offset',
'max': 6,
'min': -6,
'mode': <NumberMode.AUTO: 'auto'>,
'step': 0.1,
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
}),
'context': <ANY>,
'entity_id': 'number.test_number_floor_temperature_offset',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '0.0',
})
# ---
# name: test_number_snapshot[number.test_number_maximum_slat_angle-entry] # name: test_number_snapshot[number.test_number_maximum_slat_angle-entry]
EntityRegistryEntrySnapshot({ EntityRegistryEntrySnapshot({
'aliases': set({ 'aliases': set({
@ -639,6 +987,65 @@
'state': 'unavailable', 'state': 'unavailable',
}) })
# --- # ---
# name: test_number_snapshot[number.test_number_temperature_report_interval-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'max': 32767,
'min': 0,
'mode': <NumberMode.AUTO: 'auto'>,
'step': 1.0,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'number',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'number.test_number_temperature_report_interval',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <NumberDeviceClass.DURATION: 'duration'>,
'original_icon': None,
'original_name': 'Temperature report interval',
'platform': 'homee',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'temperature_report_interval',
'unique_id': '00055511EECC-1-19',
'unit_of_measurement': <UnitOfTime.SECONDS: 's'>,
})
# ---
# name: test_number_snapshot[number.test_number_temperature_report_interval-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'duration',
'friendly_name': 'Test Number Temperature report interval',
'max': 32767,
'min': 0,
'mode': <NumberMode.AUTO: 'auto'>,
'step': 1.0,
'unit_of_measurement': <UnitOfTime.SECONDS: 's'>,
}),
'context': <ANY>,
'entity_id': 'number.test_number_temperature_report_interval',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '60.0',
})
# ---
# name: test_number_snapshot[number.test_number_threshold_for_wind_trigger-entry] # name: test_number_snapshot[number.test_number_threshold_for_wind_trigger-entry]
EntityRegistryEntrySnapshot({ EntityRegistryEntrySnapshot({
'aliases': set({ 'aliases': set({

View File

@ -1,4 +1,61 @@
# serializer version: 1 # serializer version: 1
# name: test_select_snapshot[select.test_select_displayed_temperature-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'options': list([
'target',
'current',
]),
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'select',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'select.test_select_displayed_temperature',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Displayed temperature',
'platform': 'homee',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'display_temperature_selection',
'unique_id': '00055511EECC-1-2',
'unit_of_measurement': None,
})
# ---
# name: test_select_snapshot[select.test_select_displayed_temperature-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Test Select Displayed temperature',
'options': list([
'target',
'current',
]),
}),
'context': <ANY>,
'entity_id': 'select.test_select_displayed_temperature',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'target',
})
# ---
# name: test_select_snapshot[select.test_select_repeater_mode-entry] # name: test_select_snapshot[select.test_select_repeater_mode-entry]
EntityRegistryEntrySnapshot({ EntityRegistryEntrySnapshot({
'aliases': set({ 'aliases': set({

View File

@ -490,6 +490,62 @@
'state': '2000.0', 'state': '2000.0',
}) })
# --- # ---
# name: test_sensor_snapshot[sensor.test_multisensor_floor_temperature-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.test_multisensor_floor_temperature',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
'sensor': dict({
'suggested_display_precision': 1,
}),
}),
'original_device_class': <SensorDeviceClass.TEMPERATURE: 'temperature'>,
'original_icon': None,
'original_name': 'Floor temperature',
'platform': 'homee',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'floor_temperature',
'unique_id': '00055511EECC-1-35',
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
})
# ---
# name: test_sensor_snapshot[sensor.test_multisensor_floor_temperature-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'temperature',
'friendly_name': 'Test MultiSensor Floor temperature',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
}),
'context': <ANY>,
'entity_id': 'sensor.test_multisensor_floor_temperature',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '0.0',
})
# ---
# name: test_sensor_snapshot[sensor.test_multisensor_humidity-entry] # name: test_sensor_snapshot[sensor.test_multisensor_humidity-entry]
EntityRegistryEntrySnapshot({ EntityRegistryEntrySnapshot({
'aliases': set({ 'aliases': set({

View File

@ -177,6 +177,32 @@ async def test_current_preset_mode(
assert attributes[ATTR_PRESET_MODE] == expected assert attributes[ATTR_PRESET_MODE] == expected
@pytest.mark.parametrize(
("preset_mode_int", "expected"),
[
(10, PRESET_NONE),
(11, PRESET_NONE),
(12, PRESET_ECO),
],
)
async def test_current_preset_mode_alternate(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_homee: MagicMock,
preset_mode_int: int,
expected: str,
) -> None:
"""Test current preset mode of climate entities."""
mock_homee.nodes = [build_mock_node("thermostat_with_alternate_preset.json")]
mock_homee.get_node_by_id.return_value = mock_homee.nodes[0]
node = mock_homee.nodes[0]
node.attributes[2].current_value = preset_mode_int
await setup_integration(hass, mock_config_entry)
attributes = hass.states.get("climate.test_thermostat_5").attributes
assert attributes[ATTR_PRESET_MODE] == expected
@pytest.mark.parametrize( @pytest.mark.parametrize(
("service", "service_data", "expected"), ("service", "service_data", "expected"),
[ [
@ -250,6 +276,64 @@ async def test_climate_services(
mock_homee.set_value.assert_called_once_with(*expected) mock_homee.set_value.assert_called_once_with(*expected)
@pytest.mark.parametrize(
("service", "service_data", "expected"),
[
(
SERVICE_TURN_ON,
{},
(5, 3, 11),
),
(
SERVICE_TURN_OFF,
{},
(5, 3, 10),
),
(
SERVICE_SET_HVAC_MODE,
{ATTR_HVAC_MODE: HVACMode.HEAT},
(5, 3, 11),
),
(
SERVICE_SET_HVAC_MODE,
{ATTR_HVAC_MODE: HVACMode.OFF},
(5, 3, 10),
),
(
SERVICE_SET_PRESET_MODE,
{ATTR_PRESET_MODE: PRESET_NONE},
(5, 3, 11),
),
(
SERVICE_SET_PRESET_MODE,
{ATTR_PRESET_MODE: PRESET_ECO},
(5, 3, 12),
),
],
)
async def test_climate_services_alternate(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_homee: MagicMock,
service: str,
service_data: dict,
expected: tuple[int, int, int],
) -> None:
"""Test available services of climate entities."""
await setup_mock_climate(
hass, mock_config_entry, mock_homee, "thermostat_with_alternate_preset.json"
)
await hass.services.async_call(
CLIMATE_DOMAIN,
service,
{ATTR_ENTITY_ID: "climate.test_thermostat_5", **service_data},
blocking=True,
)
mock_homee.set_value.assert_called_once_with(*expected)
async def test_climate_snapshot( async def test_climate_snapshot(
hass: HomeAssistant, hass: HomeAssistant,
mock_homee: MagicMock, mock_homee: MagicMock,
@ -263,6 +347,7 @@ async def test_climate_snapshot(
build_mock_node("thermostat_with_currenttemp.json"), build_mock_node("thermostat_with_currenttemp.json"),
build_mock_node("thermostat_with_heating_mode.json"), build_mock_node("thermostat_with_heating_mode.json"),
build_mock_node("thermostat_with_preset.json"), build_mock_node("thermostat_with_preset.json"),
build_mock_node("thermostat_with_alternate_preset.json"),
] ]
with patch("homeassistant.components.homee.PLATFORMS", [Platform.CLIMATE]): with patch("homeassistant.components.homee.PLATFORMS", [Platform.CLIMATE]):
await setup_integration(hass, mock_config_entry) await setup_integration(hass, mock_config_entry)