Diff to HTML by rtfpessoa

Files changed (17) hide show
  1. /home/runner/work/progress/progress/clones/beta/{beta/custom_components → ha-core/homeassistant/components}/plugwise/__init__.py +22 -61
  2. /home/runner/work/progress/progress/clones/beta/{beta/custom_components → ha-core/homeassistant/components}/plugwise/binary_sensor.py +49 -81
  3. /home/runner/work/progress/progress/clones/beta/{beta/custom_components → ha-core/homeassistant/components}/plugwise/button.py +13 -55
  4. /home/runner/work/progress/progress/clones/beta/{beta/custom_components → ha-core/homeassistant/components}/plugwise/climate.py +62 -122
  5. /home/runner/work/progress/progress/clones/beta/{beta/custom_components → ha-core/homeassistant/components}/plugwise/config_flow.py +62 -182
  6. /home/runner/work/progress/progress/clones/beta/{beta/custom_components → ha-core/homeassistant/components}/plugwise/const.py +29 -156
  7. /home/runner/work/progress/progress/clones/beta/{beta/custom_components → ha-core/homeassistant/components}/plugwise/coordinator.py +38 -72
  8. /home/runner/work/progress/progress/clones/beta/{beta/custom_components → ha-core/homeassistant/components}/plugwise/diagnostics.py +1 -0
  9. /home/runner/work/progress/progress/clones/beta/{beta/custom_components → ha-core/homeassistant/components}/plugwise/entity.py +14 -25
  10. /home/runner/work/progress/progress/clones/beta/{beta/custom_components → ha-core/homeassistant/components}/plugwise/icons.json +0 -3
  11. /home/runner/work/progress/progress/clones/beta/{beta/custom_components → ha-core/homeassistant/components}/plugwise/manifest.json +5 -6
  12. /home/runner/work/progress/progress/clones/beta/{beta/custom_components → ha-core/homeassistant/components}/plugwise/number.py +23 -40
  13. /home/runner/work/progress/progress/clones/beta/{beta/custom_components → ha-core/homeassistant/components}/plugwise/select.py +23 -52
  14. /home/runner/work/progress/progress/clones/beta/{beta/custom_components → ha-core/homeassistant/components}/plugwise/sensor.py +101 -167
  15. /home/runner/work/progress/progress/clones/beta/{beta/custom_components → ha-core/homeassistant/components}/plugwise/strings.json +49 -63
  16. /home/runner/work/progress/progress/clones/beta/{beta/custom_components → ha-core/homeassistant/components}/plugwise/switch.py +24 -41
  17. /home/runner/work/progress/progress/clones/beta/{beta/custom_components → ha-core/homeassistant/components}/plugwise/util.py +3 -8
/home/runner/work/progress/progress/clones/beta/{beta/custom_components → ha-core/homeassistant/components}/plugwise/__init__.py RENAMED
@@ -1,47 +1,29 @@
1
  """Plugwise platform for Home Assistant Core."""
 
2
  from __future__ import annotations
3
 
4
  from typing import Any
5
 
6
- from plugwise.exceptions import PlugwiseError
7
- import voluptuous as vol
8
-
9
  from homeassistant.config_entries import ConfigEntry
10
  from homeassistant.const import Platform
11
- from homeassistant.core import HomeAssistant, ServiceCall, callback
12
  from homeassistant.helpers import device_registry as dr, entity_registry as er
13
 
14
- from .const import (
15
- CONF_REFRESH_INTERVAL, # pw-beta options
16
- DOMAIN,
17
- LOGGER,
18
- PLATFORMS,
19
- SERVICE_DELETE,
20
- )
21
  from .coordinator import PlugwiseDataUpdateCoordinator
22
 
23
  type PlugwiseConfigEntry = ConfigEntry[PlugwiseDataUpdateCoordinator]
24
 
25
 
26
  async def async_setup_entry(hass: HomeAssistant, entry: PlugwiseConfigEntry) -> bool:
27
- """Set up the Plugwise Device from a config entry."""
28
  await er.async_migrate_entries(hass, entry.entry_id, async_migrate_entity_entry)
29
 
30
- cooldown = 1.5 # pw-beta frontend refresh-interval
31
- if (
32
- custom_refresh := entry.options.get(CONF_REFRESH_INTERVAL)
33
- ) is not None: # pragma: no cover
34
- cooldown = custom_refresh
35
- LOGGER.debug("DUC cooldown interval: %s", cooldown)
36
-
37
- coordinator = PlugwiseDataUpdateCoordinator(
38
- hass, cooldown
39
- ) # pw-beta - cooldown, update_interval as extra
40
  await coordinator.async_config_entry_first_refresh()
41
- # Migrate a changed sensor unique_id
42
  migrate_sensor_entities(hass, coordinator)
43
 
44
- entry.runtime_data = coordinator # pw-beta
45
 
46
  device_registry = dr.async_get(hass)
47
  device_registry.async_get_or_create(
@@ -49,48 +31,21 @@
49
  identifiers={(DOMAIN, str(coordinator.api.gateway_id))},
50
  manufacturer="Plugwise",
51
  model=coordinator.api.smile_model,
 
52
  name=coordinator.api.smile_name,
53
- sw_version=coordinator.api.smile_version,
54
- )
55
-
56
- async def delete_notification(
57
- call: ServiceCall,
58
- ) -> None: # pragma: no cover # pw-beta: HA service - delete_notification
59
- """Service: delete the Plugwise Notification."""
60
- LOGGER.debug(
61
- "Service delete PW Notification called for %s",
62
- coordinator.api.smile_name,
63
- )
64
- try:
65
- await coordinator.api.delete_notification()
66
- LOGGER.debug("PW Notification deleted")
67
- except PlugwiseError:
68
- LOGGER.debug(
69
- "Failed to delete the Plugwise Notification for %s",
70
- coordinator.api.smile_name,
71
- )
72
 
73
  await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
74
- entry.async_on_unload(entry.add_update_listener(update_listener))
75
-
76
- for component in PLATFORMS: # pw-beta
77
- if component == Platform.BINARY_SENSOR:
78
- hass.services.async_register(
79
- DOMAIN, SERVICE_DELETE, delete_notification, schema=vol.Schema({})
80
- )
81
 
82
  return True
83
 
84
- async def update_listener(
85
- hass: HomeAssistant, entry: PlugwiseConfigEntry
86
- ) -> None: # pragma: no cover # pw-beta
87
- """Handle options update."""
88
- await hass.config_entries.async_reload(entry.entry_id)
89
 
90
  async def async_unload_entry(hass: HomeAssistant, entry: PlugwiseConfigEntry) -> bool:
91
- """Unload a config entry."""
92
  return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
93
 
 
94
  @callback
95
  def async_migrate_entity_entry(entry: er.RegistryEntry) -> dict[str, Any] | None:
96
  """Migrate Plugwise entity entries.
@@ -109,15 +64,15 @@
109
  "-relative_humidity"
110
  ):
111
  return {
112
- "new_unique_id": entry.unique_id.replace(
113
- "-relative_humidity", "-humidity"
114
- )
115
  }
116
  if entry.domain == Platform.SWITCH and entry.unique_id.endswith("-plug"):
117
  return {"new_unique_id": entry.unique_id.replace("-plug", "-relay")}
 
118
  # No migration needed
119
  return None
120
 
 
121
  def migrate_sensor_entities(
122
  hass: HomeAssistant,
123
  coordinator: PlugwiseDataUpdateCoordinator,
@@ -125,10 +80,10 @@
125
  """Migrate Sensors if needed."""
126
  ent_reg = er.async_get(hass)
127
 
128
- # Migrate opentherm_outdoor_temperature # pw-beta add to Core
129
  # to opentherm_outdoor_air_temperature sensor
130
  for device_id, device in coordinator.data.devices.items():
131
- if device["dev_class"] != "heater_central": # pw-beta add to Core
132
  continue
133
 
134
  old_unique_id = f"{device_id}-outdoor_temperature"
@@ -136,4 +91,10 @@
136
  Platform.SENSOR, DOMAIN, old_unique_id
137
  ):
138
  new_unique_id = f"{device_id}-outdoor_air_temperature"
 
 
 
 
 
 
139
  ent_reg.async_update_entity(entity_id, new_unique_id=new_unique_id)
 
1
  """Plugwise platform for Home Assistant Core."""
2
+
3
  from __future__ import annotations
4
 
5
  from typing import Any
6
 
 
 
 
7
  from homeassistant.config_entries import ConfigEntry
8
  from homeassistant.const import Platform
9
+ from homeassistant.core import HomeAssistant, callback
10
  from homeassistant.helpers import device_registry as dr, entity_registry as er
11
 
12
+ from .const import DOMAIN, LOGGER, PLATFORMS
 
 
 
 
 
 
13
  from .coordinator import PlugwiseDataUpdateCoordinator
14
 
15
  type PlugwiseConfigEntry = ConfigEntry[PlugwiseDataUpdateCoordinator]
16
 
17
 
18
  async def async_setup_entry(hass: HomeAssistant, entry: PlugwiseConfigEntry) -> bool:
19
+ """Set up Plugwise components from a config entry."""
20
  await er.async_migrate_entries(hass, entry.entry_id, async_migrate_entity_entry)
21
 
22
+ coordinator = PlugwiseDataUpdateCoordinator(hass)
 
 
 
 
 
 
 
 
 
23
  await coordinator.async_config_entry_first_refresh()
 
24
  migrate_sensor_entities(hass, coordinator)
25
 
26
+ entry.runtime_data = coordinator
27
 
28
  device_registry = dr.async_get(hass)
29
  device_registry.async_get_or_create(
 
31
  identifiers={(DOMAIN, str(coordinator.api.gateway_id))},
32
  manufacturer="Plugwise",
33
  model=coordinator.api.smile_model,
34
+ model_id=coordinator.api.smile_model_id,
35
  name=coordinator.api.smile_name,
36
+ sw_version=str(coordinator.api.smile_version),
37
+ ) # required for adding the entity-less P1 Gateway
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
 
39
  await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
 
 
 
 
 
 
 
40
 
41
  return True
42
 
 
 
 
 
 
43
 
44
  async def async_unload_entry(hass: HomeAssistant, entry: PlugwiseConfigEntry) -> bool:
45
+ """Unload the Plugwise components."""
46
  return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
47
 
48
+
49
  @callback
50
  def async_migrate_entity_entry(entry: er.RegistryEntry) -> dict[str, Any] | None:
51
  """Migrate Plugwise entity entries.
 
64
  "-relative_humidity"
65
  ):
66
  return {
67
+ "new_unique_id": entry.unique_id.replace("-relative_humidity", "-humidity")
 
 
68
  }
69
  if entry.domain == Platform.SWITCH and entry.unique_id.endswith("-plug"):
70
  return {"new_unique_id": entry.unique_id.replace("-plug", "-relay")}
71
+
72
  # No migration needed
73
  return None
74
 
75
+
76
  def migrate_sensor_entities(
77
  hass: HomeAssistant,
78
  coordinator: PlugwiseDataUpdateCoordinator,
 
80
  """Migrate Sensors if needed."""
81
  ent_reg = er.async_get(hass)
82
 
83
+ # Migrating opentherm_outdoor_temperature
84
  # to opentherm_outdoor_air_temperature sensor
85
  for device_id, device in coordinator.data.devices.items():
86
+ if device.get("dev_class") != "heater_central":
87
  continue
88
 
89
  old_unique_id = f"{device_id}-outdoor_temperature"
 
91
  Platform.SENSOR, DOMAIN, old_unique_id
92
  ):
93
  new_unique_id = f"{device_id}-outdoor_air_temperature"
94
+ LOGGER.debug(
95
+ "Migrating entity %s from old unique ID '%s' to new unique ID '%s'",
96
+ entity_id,
97
+ old_unique_id,
98
+ new_unique_id,
99
+ )
100
  ent_reg.async_update_entity(entity_id, new_unique_id=new_unique_id)
/home/runner/work/progress/progress/clones/beta/{beta/custom_components → ha-core/homeassistant/components}/plugwise/binary_sensor.py RENAMED
@@ -1,4 +1,5 @@
1
  """Plugwise Binary Sensor component for Home Assistant."""
 
2
  from __future__ import annotations
3
 
4
  from collections.abc import Mapping
@@ -8,33 +9,19 @@
8
  from plugwise.constants import BinarySensorType
9
 
10
  from homeassistant.components.binary_sensor import (
 
11
  BinarySensorEntity,
12
  BinarySensorEntityDescription,
13
  )
14
- from homeassistant.const import ATTR_NAME, EntityCategory
15
  from homeassistant.core import HomeAssistant, callback
16
  from homeassistant.helpers.entity_platform import AddEntitiesCallback
17
 
18
  from . import PlugwiseConfigEntry
19
- from .const import (
20
- BINARY_SENSORS,
21
- COMPRESSOR_STATE,
22
- COOLING_ENABLED,
23
- COOLING_STATE,
24
- DHW_STATE,
25
- DOMAIN,
26
- FLAME_STATE,
27
- HEATING_STATE,
28
- LOGGER,
29
- NOTIFICATIONS,
30
- PLUGWISE_NOTIFICATION,
31
- SECONDARY_BOILER_STATE,
32
- SEVERITIES,
33
- )
34
  from .coordinator import PlugwiseDataUpdateCoordinator
35
  from .entity import PlugwiseEntity
36
 
37
- PARALLEL_UPDATES = 0
38
 
39
 
40
  @dataclass(frozen=True)
@@ -44,45 +31,52 @@
44
  key: BinarySensorType
45
 
46
 
47
- PLUGWISE_BINARY_SENSORS: tuple[PlugwiseBinarySensorEntityDescription, ...] = (
 
 
 
 
 
 
48
  PlugwiseBinarySensorEntityDescription(
49
- key=COMPRESSOR_STATE,
50
- translation_key=COMPRESSOR_STATE,
51
  entity_category=EntityCategory.DIAGNOSTIC,
52
  ),
53
  PlugwiseBinarySensorEntityDescription(
54
- key=COOLING_ENABLED,
55
- translation_key=COOLING_ENABLED,
56
  entity_category=EntityCategory.DIAGNOSTIC,
57
  ),
58
  PlugwiseBinarySensorEntityDescription(
59
- key=DHW_STATE,
60
- translation_key=DHW_STATE,
61
  entity_category=EntityCategory.DIAGNOSTIC,
62
  ),
63
  PlugwiseBinarySensorEntityDescription(
64
- key=FLAME_STATE,
65
- translation_key=FLAME_STATE,
 
66
  entity_category=EntityCategory.DIAGNOSTIC,
67
  ),
68
  PlugwiseBinarySensorEntityDescription(
69
- key=HEATING_STATE,
70
- translation_key=HEATING_STATE,
71
  entity_category=EntityCategory.DIAGNOSTIC,
72
  ),
73
  PlugwiseBinarySensorEntityDescription(
74
- key=COOLING_STATE,
75
- translation_key=COOLING_STATE,
76
  entity_category=EntityCategory.DIAGNOSTIC,
77
  ),
78
  PlugwiseBinarySensorEntityDescription(
79
- key=SECONDARY_BOILER_STATE,
80
- translation_key=SECONDARY_BOILER_STATE,
81
  entity_category=EntityCategory.DIAGNOSTIC,
82
  ),
83
  PlugwiseBinarySensorEntityDescription(
84
- key=PLUGWISE_NOTIFICATION,
85
- translation_key=PLUGWISE_NOTIFICATION,
86
  entity_category=EntityCategory.DIAGNOSTIC,
87
  ),
88
  )
@@ -93,34 +87,29 @@
93
  entry: PlugwiseConfigEntry,
94
  async_add_entities: AddEntitiesCallback,
95
  ) -> None:
96
- """Set up the Plugwise binary_sensors from a ConfigEntry."""
97
  coordinator = entry.runtime_data
98
 
99
  @callback
100
- def _async_add_new_device(device_id: str) -> None:
101
- """Add new detected device during runtime."""
102
- device = coordinator.data.devices[device_id]
103
- if not (binary_sensors := device.get(BINARY_SENSORS)):
104
  return
105
 
106
- entities: list[PlugwiseBinarySensorEntity] = []
107
- for description in PLUGWISE_BINARY_SENSORS:
108
- if description.key not in binary_sensors:
109
- continue
110
- entities.append(
111
- PlugwiseBinarySensorEntity(coordinator, device_id, description)
112
- )
113
- LOGGER.debug(
114
- "Add %s %s binary sensor", device[ATTR_NAME], description.translation_key
115
  )
 
 
 
116
 
117
- async_add_entities(entities)
118
-
119
- if coordinator.new_devices:
120
- for device_id in coordinator.new_devices:
121
- _async_add_new_device(device_id)
122
- else:
123
- coordinator.new_devices_callbacks.append(_async_add_new_device)
124
 
125
 
126
  class PlugwiseBinarySensorEntity(PlugwiseEntity, BinarySensorEntity):
@@ -138,46 +127,25 @@
138
  super().__init__(coordinator, device_id)
139
  self.entity_description = description
140
  self._attr_unique_id = f"{device_id}-{description.key}"
141
- self._notification: dict[str, str] = {} # pw-beta
142
 
143
  @property
144
  def is_on(self) -> bool:
145
  """Return true if the binary sensor is on."""
146
- # pw-beta: show Plugwise notifications as HA persistent notifications
147
- if self._notification:
148
- for notify_id, message in self._notification.items():
149
- self.hass.components.persistent_notification.async_create(
150
- message, "Plugwise Notification:", f"{DOMAIN}.{notify_id}"
151
- )
152
-
153
- # return self.device["binary_sensors"][self.entity_description.key] # type: ignore [literal-required]
154
  return self.device["binary_sensors"][self.entity_description.key]
155
 
156
  @property
157
  def extra_state_attributes(self) -> Mapping[str, Any] | None:
158
  """Return entity specific state attributes."""
159
- if self.entity_description.key != PLUGWISE_NOTIFICATION:
160
  return None
161
 
162
- # pw-beta adjustment with attrs is to only represent severities *with* content
163
- # not all severities including those without content as empty lists
164
- attrs: dict[str, list[str]] = {} # pw-beta Re-evaluate against Core
165
- self._notification = {} # pw-beta
166
- if notify := self.coordinator.data.gateway[NOTIFICATIONS]:
167
- for notify_id, details in notify.items(): # pw-beta uses notify_id
168
  for msg_type, msg in details.items():
169
  msg_type = msg_type.lower()
170
  if msg_type not in SEVERITIES:
171
- msg_type = "other" # pragma: no cover
172
-
173
- if (
174
- f"{msg_type}_msg" not in attrs
175
- ): # pw-beta Re-evaluate against Core
176
- attrs[f"{msg_type}_msg"] = []
177
  attrs[f"{msg_type}_msg"].append(msg)
178
 
179
- self._notification[
180
- notify_id
181
- ] = f"{msg_type.title()}: {msg}" # pw-beta
182
-
183
  return attrs
 
1
  """Plugwise Binary Sensor component for Home Assistant."""
2
+
3
  from __future__ import annotations
4
 
5
  from collections.abc import Mapping
 
9
  from plugwise.constants import BinarySensorType
10
 
11
  from homeassistant.components.binary_sensor import (
12
+ BinarySensorDeviceClass,
13
  BinarySensorEntity,
14
  BinarySensorEntityDescription,
15
  )
16
+ from homeassistant.const import EntityCategory
17
  from homeassistant.core import HomeAssistant, callback
18
  from homeassistant.helpers.entity_platform import AddEntitiesCallback
19
 
20
  from . import PlugwiseConfigEntry
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  from .coordinator import PlugwiseDataUpdateCoordinator
22
  from .entity import PlugwiseEntity
23
 
24
+ SEVERITIES = ["other", "info", "warning", "error"]
25
 
26
 
27
  @dataclass(frozen=True)
 
31
  key: BinarySensorType
32
 
33
 
34
+ BINARY_SENSORS: tuple[PlugwiseBinarySensorEntityDescription, ...] = (
35
+ PlugwiseBinarySensorEntityDescription(
36
+ key="low_battery",
37
+ translation_key="low_battery",
38
+ device_class=BinarySensorDeviceClass.BATTERY,
39
+ entity_category=EntityCategory.DIAGNOSTIC,
40
+ ),
41
  PlugwiseBinarySensorEntityDescription(
42
+ key="compressor_state",
43
+ translation_key="compressor_state",
44
  entity_category=EntityCategory.DIAGNOSTIC,
45
  ),
46
  PlugwiseBinarySensorEntityDescription(
47
+ key="cooling_enabled",
48
+ translation_key="cooling_enabled",
49
  entity_category=EntityCategory.DIAGNOSTIC,
50
  ),
51
  PlugwiseBinarySensorEntityDescription(
52
+ key="dhw_state",
53
+ translation_key="dhw_state",
54
  entity_category=EntityCategory.DIAGNOSTIC,
55
  ),
56
  PlugwiseBinarySensorEntityDescription(
57
+ key="flame_state",
58
+ translation_key="flame_state",
59
+ name="Flame state",
60
  entity_category=EntityCategory.DIAGNOSTIC,
61
  ),
62
  PlugwiseBinarySensorEntityDescription(
63
+ key="heating_state",
64
+ translation_key="heating_state",
65
  entity_category=EntityCategory.DIAGNOSTIC,
66
  ),
67
  PlugwiseBinarySensorEntityDescription(
68
+ key="cooling_state",
69
+ translation_key="cooling_state",
70
  entity_category=EntityCategory.DIAGNOSTIC,
71
  ),
72
  PlugwiseBinarySensorEntityDescription(
73
+ key="secondary_boiler_state",
74
+ translation_key="secondary_boiler_state",
75
  entity_category=EntityCategory.DIAGNOSTIC,
76
  ),
77
  PlugwiseBinarySensorEntityDescription(
78
+ key="plugwise_notification",
79
+ translation_key="plugwise_notification",
80
  entity_category=EntityCategory.DIAGNOSTIC,
81
  ),
82
  )
 
87
  entry: PlugwiseConfigEntry,
88
  async_add_entities: AddEntitiesCallback,
89
  ) -> None:
90
+ """Set up the Smile binary_sensors from a config entry."""
91
  coordinator = entry.runtime_data
92
 
93
  @callback
94
+ def _add_entities() -> None:
95
+ """Add Entities."""
96
+ if not coordinator.new_devices:
 
97
  return
98
 
99
+ async_add_entities(
100
+ PlugwiseBinarySensorEntity(coordinator, device_id, description)
101
+ for device_id in coordinator.new_devices
102
+ if (
103
+ binary_sensors := coordinator.data.devices[device_id].get(
104
+ "binary_sensors"
105
+ )
 
 
106
  )
107
+ for description in BINARY_SENSORS
108
+ if description.key in binary_sensors
109
+ )
110
 
111
+ _add_entities()
112
+ entry.async_on_unload(coordinator.async_add_listener(_add_entities))
 
 
 
 
 
113
 
114
 
115
  class PlugwiseBinarySensorEntity(PlugwiseEntity, BinarySensorEntity):
 
127
  super().__init__(coordinator, device_id)
128
  self.entity_description = description
129
  self._attr_unique_id = f"{device_id}-{description.key}"
 
130
 
131
  @property
132
  def is_on(self) -> bool:
133
  """Return true if the binary sensor is on."""
 
 
 
 
 
 
 
 
134
  return self.device["binary_sensors"][self.entity_description.key]
135
 
136
  @property
137
  def extra_state_attributes(self) -> Mapping[str, Any] | None:
138
  """Return entity specific state attributes."""
139
+ if self.entity_description.key != "plugwise_notification":
140
  return None
141
 
142
+ attrs: dict[str, list[str]] = {f"{severity}_msg": [] for severity in SEVERITIES}
143
+ if notify := self.coordinator.data.gateway["notifications"]:
144
+ for details in notify.values():
 
 
 
145
  for msg_type, msg in details.items():
146
  msg_type = msg_type.lower()
147
  if msg_type not in SEVERITIES:
148
+ msg_type = "other"
 
 
 
 
 
149
  attrs[f"{msg_type}_msg"].append(msg)
150
 
 
 
 
 
151
  return attrs
/home/runner/work/progress/progress/clones/beta/{beta/custom_components → ha-core/homeassistant/components}/plugwise/button.py RENAMED
@@ -1,30 +1,18 @@
1
  """Plugwise Button component for Home Assistant."""
 
2
  from __future__ import annotations
3
 
4
- from homeassistant.components.button import (
5
- ButtonDeviceClass,
6
- ButtonEntity,
7
- ButtonEntityDescription,
8
- )
9
- from homeassistant.const import ATTR_NAME, EntityCategory
10
  from homeassistant.core import HomeAssistant
11
  from homeassistant.helpers.entity_platform import AddEntitiesCallback
12
 
13
  from . import PlugwiseConfigEntry
14
- from .const import GATEWAY_ID, LOGGER, REBOOT
15
  from .coordinator import PlugwiseDataUpdateCoordinator
16
  from .entity import PlugwiseEntity
17
  from .util import plugwise_command
18
 
19
- BUTTON_TYPES: tuple[ButtonEntityDescription, ...] = (
20
- ButtonEntityDescription(
21
- key=REBOOT,
22
- translation_key=REBOOT,
23
- device_class=ButtonDeviceClass.RESTART,
24
- entity_category=EntityCategory.CONFIG,
25
- ),
26
- )
27
-
28
 
29
  async def async_setup_entry(
30
  hass: HomeAssistant,
@@ -34,59 +22,29 @@
34
  """Set up the Plugwise buttons from a ConfigEntry."""
35
  coordinator = entry.runtime_data
36
 
37
- entities: list[PlugwiseButtonEntity] = []
38
  gateway = coordinator.data.gateway
39
- for device_id, device in coordinator.data.devices.items():
40
- if device_id == gateway[GATEWAY_ID] and REBOOT in gateway:
41
- for description in BUTTON_TYPES:
42
- entities.append(
43
- PlugwiseButtonEntity(
44
- coordinator,
45
- device_id,
46
- description,
47
- )
48
- )
49
- LOGGER.debug(
50
- "Add %s %s button", device[ATTR_NAME], description.key
51
- )
52
-
53
- async_add_entities(entities)
54
-
55
- def _async_add_new_device(device_id: str) -> None:
56
- """Add new detected device during runtime."""
57
- device = coordinator.data.devices[device_id]
58
- entities: list[PlugwiseButtonEntity] = []
59
- gateway = coordinator.data.gateway
60
- if device_id == gateway[GATEWAY_ID] and REBOOT in gateway:
61
- for description in BUTTON_TYPES:
62
- entities.append(
63
- PlugwiseButtonEntity(coordinator, device_id, description)
64
- )
65
- LOGGER.debug(
66
- "Add %s %s button", device[ATTR_NAME], description.translation_key
67
- )
68
-
69
- async_add_entities(entities)
70
-
71
- coordinator.new_devices_callbacks.append(_async_add_new_device)
72
 
73
 
74
  class PlugwiseButtonEntity(PlugwiseEntity, ButtonEntity):
75
  """Defines a Plugwise button."""
76
 
77
- entity_description: ButtonEntityDescription
 
78
 
79
  def __init__(
80
  self,
81
  coordinator: PlugwiseDataUpdateCoordinator,
82
  device_id: str,
83
- description: ButtonEntityDescription,
84
  ) -> None:
85
  """Initialize the button."""
86
  super().__init__(coordinator, device_id)
87
- self.device_id = device_id
88
- self.entity_description = description
89
- self._attr_unique_id = f"{device_id}-{description.key}"
90
 
91
  @plugwise_command
92
  async def async_press(self) -> None:
 
1
  """Plugwise Button component for Home Assistant."""
2
+
3
  from __future__ import annotations
4
 
5
+ from homeassistant.components.button import ButtonDeviceClass, ButtonEntity
6
+ from homeassistant.const import EntityCategory
 
 
 
 
7
  from homeassistant.core import HomeAssistant
8
  from homeassistant.helpers.entity_platform import AddEntitiesCallback
9
 
10
  from . import PlugwiseConfigEntry
11
+ from .const import GATEWAY_ID, REBOOT
12
  from .coordinator import PlugwiseDataUpdateCoordinator
13
  from .entity import PlugwiseEntity
14
  from .util import plugwise_command
15
 
 
 
 
 
 
 
 
 
 
16
 
17
  async def async_setup_entry(
18
  hass: HomeAssistant,
 
22
  """Set up the Plugwise buttons from a ConfigEntry."""
23
  coordinator = entry.runtime_data
24
 
 
25
  gateway = coordinator.data.gateway
26
+ async_add_entities(
27
+ PlugwiseButtonEntity(coordinator, device_id)
28
+ for device_id in coordinator.data.devices
29
+ if device_id == gateway[GATEWAY_ID] and REBOOT in gateway
30
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
 
32
 
33
  class PlugwiseButtonEntity(PlugwiseEntity, ButtonEntity):
34
  """Defines a Plugwise button."""
35
 
36
+ _attr_device_class = ButtonDeviceClass.RESTART
37
+ _attr_entity_category = EntityCategory.CONFIG
38
 
39
  def __init__(
40
  self,
41
  coordinator: PlugwiseDataUpdateCoordinator,
42
  device_id: str,
 
43
  ) -> None:
44
  """Initialize the button."""
45
  super().__init__(coordinator, device_id)
46
+ self._attr_translation_key = REBOOT
47
+ self._attr_unique_id = f"{device_id}-reboot"
 
48
 
49
  @plugwise_command
50
  async def async_press(self) -> None:
/home/runner/work/progress/progress/clones/beta/{beta/custom_components → ha-core/homeassistant/components}/plugwise/climate.py RENAMED
@@ -1,52 +1,25 @@
1
  """Plugwise Climate component for Home Assistant."""
 
2
  from __future__ import annotations
3
 
4
  from typing import Any
5
 
6
- from homeassistant.components.climate import ClimateEntity
7
- from homeassistant.components.climate.const import (
8
  ATTR_HVAC_MODE,
9
  ATTR_TARGET_TEMP_HIGH,
10
  ATTR_TARGET_TEMP_LOW,
11
- PRESET_AWAY, # pw-beta homekit emulation
12
- PRESET_HOME, # pw-beta homekit emulation
13
  ClimateEntityFeature,
14
  HVACAction,
15
  HVACMode,
16
  )
17
- from homeassistant.const import ATTR_TEMPERATURE, STATE_OFF, STATE_ON, UnitOfTemperature
18
  from homeassistant.core import HomeAssistant, callback
19
  from homeassistant.exceptions import HomeAssistantError
20
  from homeassistant.helpers.entity_platform import AddEntitiesCallback
21
 
22
  from . import PlugwiseConfigEntry
23
- from .const import (
24
- ACTIVE_PRESET,
25
- AVAILABLE_SCHEDULES,
26
- BINARY_SENSORS,
27
- CONF_HOMEKIT_EMULATION, # pw-beta homekit emulation
28
- CONTROL_STATE,
29
- COOLING_PRESENT,
30
- COOLING_STATE,
31
- DEV_CLASS,
32
- DOMAIN,
33
- GATEWAY_ID,
34
- HEATING_STATE,
35
- LOCATION,
36
- LOWER_BOUND,
37
- MASTER_THERMOSTATS,
38
- MODE,
39
- REGULATION_MODES,
40
- RESOLUTION,
41
- SELECT_REGULATION_MODE,
42
- SENSORS,
43
- SMILE_NAME,
44
- TARGET_TEMP,
45
- TARGET_TEMP_HIGH,
46
- TARGET_TEMP_LOW,
47
- THERMOSTAT,
48
- UPPER_BOUND,
49
- )
50
  from .coordinator import PlugwiseDataUpdateCoordinator
51
  from .entity import PlugwiseEntity
52
  from .util import plugwise_command
@@ -57,11 +30,8 @@
57
  entry: PlugwiseConfigEntry,
58
  async_add_entities: AddEntitiesCallback,
59
  ) -> None:
60
- """Set up the Smile Thermostats from a ConfigEntry."""
61
  coordinator = entry.runtime_data
62
- homekit_enabled: bool = entry.options.get(
63
- CONF_HOMEKIT_EMULATION, False
64
- ) # pw-beta homekit emulation
65
 
66
  @callback
67
  def _add_entities() -> None:
@@ -70,16 +40,13 @@
70
  return
71
 
72
  async_add_entities(
73
- PlugwiseClimateEntity(
74
- coordinator, device_id, homekit_enabled
75
- ) # pw-beta homekit emulation
76
- for device_id, device in coordinator.data.devices.items()
77
- if device[DEV_CLASS] in MASTER_THERMOSTATS
78
  )
79
 
80
- entry.async_on_unload(coordinator.async_add_listener(_add_entities))
81
-
82
  _add_entities()
 
83
 
84
 
85
  class PlugwiseClimateEntity(PlugwiseEntity, ClimateEntity):
@@ -91,36 +58,25 @@
91
  _attr_translation_key = DOMAIN
92
  _enable_turn_on_off_backwards_compatibility = False
93
 
94
- _homekit_mode: str | None = None # pw-beta homekit emulation
95
- _previous_mode: str = HVACAction.HEATING
96
 
97
  def __init__(
98
  self,
99
  coordinator: PlugwiseDataUpdateCoordinator,
100
  device_id: str,
101
- homekit_enabled: bool, # pw-beta homekit emulation
102
  ) -> None:
103
  """Set up the Plugwise API."""
104
  super().__init__(coordinator, device_id)
105
-
106
- self._homekit_enabled = homekit_enabled # pw-beta homekit emulation
107
- gateway_id: str = coordinator.data.gateway[GATEWAY_ID]
108
- self.gateway_data = coordinator.data.devices[gateway_id]
109
-
110
- self._attr_max_temp = min(self.device[THERMOSTAT][UPPER_BOUND], 35.0)
111
- self._attr_min_temp = self.device[THERMOSTAT][LOWER_BOUND]
112
- # Ensure we don't drop below 0.1
113
- self._attr_target_temperature_step = max(
114
- self.device[THERMOSTAT][RESOLUTION], 0.1
115
- )
116
  self._attr_unique_id = f"{device_id}-climate"
117
-
118
- # Determine supported features
119
  self.cdr_gateway = coordinator.data.gateway
 
 
 
120
  self._attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE
121
  if (
122
- self.cdr_gateway[COOLING_PRESENT]
123
- and self.cdr_gateway[SMILE_NAME] != "Adam"
124
  ):
125
  self._attr_supported_features = (
126
  ClimateEntityFeature.TARGET_TEMPERATURE_RANGE
@@ -129,9 +85,16 @@
129
  self._attr_supported_features |= (
130
  ClimateEntityFeature.TURN_OFF | ClimateEntityFeature.TURN_ON
131
  )
132
- if presets := self.device["preset_modes"]: # can be NONE
133
  self._attr_supported_features |= ClimateEntityFeature.PRESET_MODE
134
- self._attr_preset_modes = presets
 
 
 
 
 
 
 
135
 
136
  def _previous_action_mode(self, coordinator: PlugwiseDataUpdateCoordinator) -> None:
137
  """Return the previous action-mode when the regulation-mode is not heating or cooling.
@@ -140,17 +103,17 @@
140
  """
141
  # When no cooling available, _previous_mode is always heating
142
  if (
143
- REGULATION_MODES in self.gateway_data
144
- and HVACAction.COOLING in self.gateway_data[REGULATION_MODES]
145
  ):
146
- mode = self.gateway_data[SELECT_REGULATION_MODE]
147
- if mode in (HVACAction.COOLING, HVACAction.HEATING):
148
  self._previous_mode = mode
149
 
150
  @property
151
  def current_temperature(self) -> float:
152
  """Return the current temperature."""
153
- return self.device[SENSORS][ATTR_TEMPERATURE]
154
 
155
  @property
156
  def target_temperature(self) -> float:
@@ -159,7 +122,7 @@
159
  Connected to the HVACMode combination of AUTO-HEAT.
160
  """
161
 
162
- return self.device[THERMOSTAT][TARGET_TEMP]
163
 
164
  @property
165
  def target_temperature_high(self) -> float:
@@ -167,7 +130,7 @@
167
 
168
  Connected to the HVACMode combination of AUTO-HEAT_COOL.
169
  """
170
- return self.device[THERMOSTAT][TARGET_TEMP_HIGH]
171
 
172
  @property
173
  def target_temperature_low(self) -> float:
@@ -175,39 +138,30 @@
175
 
176
  Connected to the HVACMode combination AUTO-HEAT_COOL.
177
  """
178
- return self.device[THERMOSTAT][TARGET_TEMP_LOW]
179
 
180
  @property
181
  def hvac_mode(self) -> HVACMode:
182
  """Return HVAC operation ie. auto, cool, heat, heat_cool, or off mode."""
183
- if (
184
- mode := self.device[MODE]
185
- ) is None or mode not in self.hvac_modes: # pw-beta add to Core
186
- return HVACMode.HEAT # pragma: no cover
187
- # pw-beta homekit emulation
188
- if self._homekit_enabled and self._homekit_mode == HVACMode.OFF:
189
- mode = HVACMode.OFF # pragma: no cover
190
-
191
  return HVACMode(mode)
192
 
193
  @property
194
  def hvac_modes(self) -> list[HVACMode]:
195
  """Return a list of available HVACModes."""
196
  hvac_modes: list[HVACMode] = []
197
- if (
198
- self._homekit_enabled # pw-beta homekit emulation
199
- or REGULATION_MODES in self.gateway_data
200
- ):
201
  hvac_modes.append(HVACMode.OFF)
202
 
203
- if AVAILABLE_SCHEDULES in self.device:
204
  hvac_modes.append(HVACMode.AUTO)
205
 
206
- if self.cdr_gateway[COOLING_PRESENT]:
207
- if REGULATION_MODES in self.gateway_data:
208
- if self.gateway_data[SELECT_REGULATION_MODE] == HVACAction.COOLING:
209
  hvac_modes.append(HVACMode.COOL)
210
- if self.gateway_data[SELECT_REGULATION_MODE] == HVACAction.HEATING:
211
  hvac_modes.append(HVACMode.HEAT)
212
  else:
213
  hvac_modes.append(HVACMode.HEAT_COOL)
@@ -217,27 +171,26 @@
217
  return hvac_modes
218
 
219
  @property
220
- def hvac_action(self) -> HVACAction: # pw-beta add to Core
221
  """Return the current running hvac operation if supported."""
222
  # Keep track of the previous action-mode
223
  self._previous_action_mode(self.coordinator)
224
 
225
  # Adam provides the hvac_action for each thermostat
226
- if (control_state := self.device.get(CONTROL_STATE)) == HVACAction.COOLING:
227
  return HVACAction.COOLING
228
- if control_state == HVACAction.HEATING:
229
  return HVACAction.HEATING
230
- if control_state == HVACAction.PREHEATING:
231
  return HVACAction.PREHEATING
232
- if control_state == STATE_OFF:
233
  return HVACAction.IDLE
234
 
235
- # Anna
236
  heater: str = self.coordinator.data.gateway["heater_id"]
237
  heater_data = self.coordinator.data.devices[heater]
238
- if heater_data[BINARY_SENSORS][HEATING_STATE]:
239
  return HVACAction.HEATING
240
- if heater_data[BINARY_SENSORS].get(COOLING_STATE, False):
241
  return HVACAction.COOLING
242
 
243
  return HVACAction.IDLE
@@ -245,18 +198,18 @@
245
  @property
246
  def preset_mode(self) -> str | None:
247
  """Return the current preset mode."""
248
- return self.device[ACTIVE_PRESET]
249
 
250
  @plugwise_command
251
  async def async_set_temperature(self, **kwargs: Any) -> None:
252
  """Set new target temperature."""
253
  data: dict[str, Any] = {}
254
  if ATTR_TEMPERATURE in kwargs:
255
- data[TARGET_TEMP] = kwargs.get(ATTR_TEMPERATURE)
256
  if ATTR_TARGET_TEMP_HIGH in kwargs:
257
- data[TARGET_TEMP_HIGH] = kwargs.get(ATTR_TARGET_TEMP_HIGH)
258
  if ATTR_TARGET_TEMP_LOW in kwargs:
259
- data[TARGET_TEMP_LOW] = kwargs.get(ATTR_TARGET_TEMP_LOW)
260
 
261
  for temperature in data.values():
262
  if temperature is None or not (
@@ -267,10 +220,10 @@
267
  if mode := kwargs.get(ATTR_HVAC_MODE):
268
  await self.async_set_hvac_mode(mode)
269
 
270
- await self.coordinator.api.set_temperature(self.device[LOCATION], data)
271
 
272
  @plugwise_command
273
- async def async_set_hvac_mode(self, hvac_mode: str) -> None:
274
  """Set the hvac mode."""
275
  if hvac_mode not in self.hvac_modes:
276
  raise HomeAssistantError("Unsupported hvac_mode")
@@ -278,30 +231,17 @@
278
  if hvac_mode == self.hvac_mode:
279
  return
280
 
281
- if hvac_mode != HVACMode.OFF:
 
 
282
  await self.coordinator.api.set_schedule_state(
283
- self.device[LOCATION],
284
- STATE_ON if hvac_mode == HVACMode.AUTO else STATE_OFF,
285
  )
286
-
287
- if (
288
- not self._homekit_enabled
289
- ): # pw-beta: feature request - mimic HomeKit behavior
290
- if hvac_mode == HVACMode.OFF:
291
- await self.coordinator.api.set_regulation_mode(hvac_mode)
292
- elif self.hvac_mode == HVACMode.OFF:
293
  await self.coordinator.api.set_regulation_mode(self._previous_mode)
294
- else:
295
- self._homekit_mode = hvac_mode # pragma: no cover
296
- if self._homekit_mode == HVACMode.OFF: # pragma: no cover
297
- await self.async_set_preset_mode(PRESET_AWAY) # pragma: no cover
298
- if (
299
- self._homekit_mode in [HVACMode.HEAT, HVACMode.HEAT_COOL]
300
- and self.device[ACTIVE_PRESET] == PRESET_AWAY
301
- ): # pragma: no cover
302
- await self.async_set_preset_mode(PRESET_HOME) # pragma: no cover
303
 
304
  @plugwise_command
305
  async def async_set_preset_mode(self, preset_mode: str) -> None:
306
  """Set the preset mode."""
307
- await self.coordinator.api.set_preset(self.device[LOCATION], preset_mode)
 
1
  """Plugwise Climate component for Home Assistant."""
2
+
3
  from __future__ import annotations
4
 
5
  from typing import Any
6
 
7
+ from homeassistant.components.climate import (
 
8
  ATTR_HVAC_MODE,
9
  ATTR_TARGET_TEMP_HIGH,
10
  ATTR_TARGET_TEMP_LOW,
11
+ ClimateEntity,
 
12
  ClimateEntityFeature,
13
  HVACAction,
14
  HVACMode,
15
  )
16
+ from homeassistant.const import ATTR_TEMPERATURE, UnitOfTemperature
17
  from homeassistant.core import HomeAssistant, callback
18
  from homeassistant.exceptions import HomeAssistantError
19
  from homeassistant.helpers.entity_platform import AddEntitiesCallback
20
 
21
  from . import PlugwiseConfigEntry
22
+ from .const import DOMAIN, MASTER_THERMOSTATS
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  from .coordinator import PlugwiseDataUpdateCoordinator
24
  from .entity import PlugwiseEntity
25
  from .util import plugwise_command
 
30
  entry: PlugwiseConfigEntry,
31
  async_add_entities: AddEntitiesCallback,
32
  ) -> None:
33
+ """Set up the Smile Thermostats from a config entry."""
34
  coordinator = entry.runtime_data
 
 
 
35
 
36
  @callback
37
  def _add_entities() -> None:
 
40
  return
41
 
42
  async_add_entities(
43
+ PlugwiseClimateEntity(coordinator, device_id)
44
+ for device_id in coordinator.new_devices
45
+ if coordinator.data.devices[device_id]["dev_class"] in MASTER_THERMOSTATS
 
 
46
  )
47
 
 
 
48
  _add_entities()
49
+ entry.async_on_unload(coordinator.async_add_listener(_add_entities))
50
 
51
 
52
  class PlugwiseClimateEntity(PlugwiseEntity, ClimateEntity):
 
58
  _attr_translation_key = DOMAIN
59
  _enable_turn_on_off_backwards_compatibility = False
60
 
61
+ _previous_mode: str = "heating"
 
62
 
63
  def __init__(
64
  self,
65
  coordinator: PlugwiseDataUpdateCoordinator,
66
  device_id: str,
 
67
  ) -> None:
68
  """Set up the Plugwise API."""
69
  super().__init__(coordinator, device_id)
70
+ self._attr_extra_state_attributes = {}
 
 
 
 
 
 
 
 
 
 
71
  self._attr_unique_id = f"{device_id}-climate"
 
 
72
  self.cdr_gateway = coordinator.data.gateway
73
+ gateway_id: str = coordinator.data.gateway["gateway_id"]
74
+ self.gateway_data = coordinator.data.devices[gateway_id]
75
+ # Determine supported features
76
  self._attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE
77
  if (
78
+ self.cdr_gateway["cooling_present"]
79
+ and self.cdr_gateway["smile_name"] != "Adam"
80
  ):
81
  self._attr_supported_features = (
82
  ClimateEntityFeature.TARGET_TEMPERATURE_RANGE
 
85
  self._attr_supported_features |= (
86
  ClimateEntityFeature.TURN_OFF | ClimateEntityFeature.TURN_ON
87
  )
88
+ if presets := self.device.get("preset_modes"):
89
  self._attr_supported_features |= ClimateEntityFeature.PRESET_MODE
90
+ self._attr_preset_modes = presets
91
+
92
+ self._attr_min_temp = self.device["thermostat"]["lower_bound"]
93
+ self._attr_max_temp = min(self.device["thermostat"]["upper_bound"], 35.0)
94
+ # Ensure we don't drop below 0.1
95
+ self._attr_target_temperature_step = max(
96
+ self.device["thermostat"]["resolution"], 0.1
97
+ )
98
 
99
  def _previous_action_mode(self, coordinator: PlugwiseDataUpdateCoordinator) -> None:
100
  """Return the previous action-mode when the regulation-mode is not heating or cooling.
 
103
  """
104
  # When no cooling available, _previous_mode is always heating
105
  if (
106
+ "regulation_modes" in self.gateway_data
107
+ and "cooling" in self.gateway_data["regulation_modes"]
108
  ):
109
+ mode = self.gateway_data["select_regulation_mode"]
110
+ if mode in ("cooling", "heating"):
111
  self._previous_mode = mode
112
 
113
  @property
114
  def current_temperature(self) -> float:
115
  """Return the current temperature."""
116
+ return self.device["sensors"]["temperature"]
117
 
118
  @property
119
  def target_temperature(self) -> float:
 
122
  Connected to the HVACMode combination of AUTO-HEAT.
123
  """
124
 
125
+ return self.device["thermostat"]["setpoint"]
126
 
127
  @property
128
  def target_temperature_high(self) -> float:
 
130
 
131
  Connected to the HVACMode combination of AUTO-HEAT_COOL.
132
  """
133
+ return self.device["thermostat"]["setpoint_high"]
134
 
135
  @property
136
  def target_temperature_low(self) -> float:
 
138
 
139
  Connected to the HVACMode combination AUTO-HEAT_COOL.
140
  """
141
+ return self.device["thermostat"]["setpoint_low"]
142
 
143
  @property
144
  def hvac_mode(self) -> HVACMode:
145
  """Return HVAC operation ie. auto, cool, heat, heat_cool, or off mode."""
146
+ if (mode := self.device.get("mode")) is None or mode not in self.hvac_modes:
147
+ return HVACMode.HEAT
 
 
 
 
 
 
148
  return HVACMode(mode)
149
 
150
  @property
151
  def hvac_modes(self) -> list[HVACMode]:
152
  """Return a list of available HVACModes."""
153
  hvac_modes: list[HVACMode] = []
154
+ if "regulation_modes" in self.gateway_data:
 
 
 
155
  hvac_modes.append(HVACMode.OFF)
156
 
157
+ if "available_schedules" in self.device:
158
  hvac_modes.append(HVACMode.AUTO)
159
 
160
+ if self.cdr_gateway["cooling_present"]:
161
+ if "regulation_modes" in self.gateway_data:
162
+ if self.gateway_data["select_regulation_mode"] == "cooling":
163
  hvac_modes.append(HVACMode.COOL)
164
+ if self.gateway_data["select_regulation_mode"] == "heating":
165
  hvac_modes.append(HVACMode.HEAT)
166
  else:
167
  hvac_modes.append(HVACMode.HEAT_COOL)
 
171
  return hvac_modes
172
 
173
  @property
174
+ def hvac_action(self) -> HVACAction:
175
  """Return the current running hvac operation if supported."""
176
  # Keep track of the previous action-mode
177
  self._previous_action_mode(self.coordinator)
178
 
179
  # Adam provides the hvac_action for each thermostat
180
+ if (control_state := self.device.get("control_state")) == "cooling":
181
  return HVACAction.COOLING
182
+ if control_state == "heating":
183
  return HVACAction.HEATING
184
+ if control_state == "preheating":
185
  return HVACAction.PREHEATING
186
+ if control_state == "off":
187
  return HVACAction.IDLE
188
 
 
189
  heater: str = self.coordinator.data.gateway["heater_id"]
190
  heater_data = self.coordinator.data.devices[heater]
191
+ if heater_data["binary_sensors"]["heating_state"]:
192
  return HVACAction.HEATING
193
+ if heater_data["binary_sensors"].get("cooling_state", False):
194
  return HVACAction.COOLING
195
 
196
  return HVACAction.IDLE
 
198
  @property
199
  def preset_mode(self) -> str | None:
200
  """Return the current preset mode."""
201
+ return self.device.get("active_preset")
202
 
203
  @plugwise_command
204
  async def async_set_temperature(self, **kwargs: Any) -> None:
205
  """Set new target temperature."""
206
  data: dict[str, Any] = {}
207
  if ATTR_TEMPERATURE in kwargs:
208
+ data["setpoint"] = kwargs.get(ATTR_TEMPERATURE)
209
  if ATTR_TARGET_TEMP_HIGH in kwargs:
210
+ data["setpoint_high"] = kwargs.get(ATTR_TARGET_TEMP_HIGH)
211
  if ATTR_TARGET_TEMP_LOW in kwargs:
212
+ data["setpoint_low"] = kwargs.get(ATTR_TARGET_TEMP_LOW)
213
 
214
  for temperature in data.values():
215
  if temperature is None or not (
 
220
  if mode := kwargs.get(ATTR_HVAC_MODE):
221
  await self.async_set_hvac_mode(mode)
222
 
223
+ await self.coordinator.api.set_temperature(self.device["location"], data)
224
 
225
  @plugwise_command
226
+ async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
227
  """Set the hvac mode."""
228
  if hvac_mode not in self.hvac_modes:
229
  raise HomeAssistantError("Unsupported hvac_mode")
 
231
  if hvac_mode == self.hvac_mode:
232
  return
233
 
234
+ if hvac_mode == HVACMode.OFF:
235
+ await self.coordinator.api.set_regulation_mode(hvac_mode)
236
+ else:
237
  await self.coordinator.api.set_schedule_state(
238
+ self.device["location"],
239
+ "on" if hvac_mode == HVACMode.AUTO else "off",
240
  )
241
+ if self.hvac_mode == HVACMode.OFF:
 
 
 
 
 
 
242
  await self.coordinator.api.set_regulation_mode(self._previous_mode)
 
 
 
 
 
 
 
 
 
243
 
244
  @plugwise_command
245
  async def async_set_preset_mode(self, preset_mode: str) -> None:
246
  """Set the preset mode."""
247
+ await self.coordinator.api.set_preset(self.device["location"], preset_mode)
/home/runner/work/progress/progress/clones/beta/{beta/custom_components → ha-core/homeassistant/components}/plugwise/config_flow.py RENAMED
@@ -1,8 +1,8 @@
1
  """Config flow for Plugwise integration."""
 
2
  from __future__ import annotations
3
 
4
- import datetime as dt # pw-beta options
5
- from typing import Any
6
 
7
  from plugwise import Smile
8
  from plugwise.exceptions import (
@@ -16,14 +16,7 @@
16
  import voluptuous as vol
17
 
18
  from homeassistant.components.zeroconf import ZeroconfServiceInfo
19
- from homeassistant.config_entries import (
20
- SOURCE_USER,
21
- ConfigEntry,
22
- ConfigFlow,
23
- ConfigFlowResult,
24
- OptionsFlow,
25
- OptionsFlowWithConfigEntry,
26
- )
27
  from homeassistant.const import (
28
  ATTR_CONFIGURATION_URL,
29
  CONF_BASE,
@@ -31,75 +24,46 @@
31
  CONF_NAME,
32
  CONF_PASSWORD,
33
  CONF_PORT,
34
- CONF_SCAN_INTERVAL,
35
  CONF_USERNAME,
36
  )
37
- from homeassistant.core import HomeAssistant, callback
38
- from homeassistant.helpers import config_validation as cv
39
  from homeassistant.helpers.aiohttp_client import async_get_clientsession
40
 
41
  from .const import (
42
- ANNA_WITH_ADAM,
43
- CONF_HOMEKIT_EMULATION, # pw-beta option
44
- CONF_REFRESH_INTERVAL, # pw-beta option
45
- CONTEXT,
46
- COORDINATOR,
47
  DEFAULT_PORT,
48
- DEFAULT_SCAN_INTERVAL, # pw-beta option
49
  DEFAULT_USERNAME,
50
  DOMAIN,
51
- FLOW_ID,
52
  FLOW_SMILE,
53
  FLOW_STRETCH,
54
- INIT,
55
- PRODUCT,
56
  SMILE,
57
- SMILE_OPEN_THERM,
58
- SMILE_THERMO,
59
  STRETCH,
60
  STRETCH_USERNAME,
61
- THERMOSTAT,
62
- TITLE_PLACEHOLDERS,
63
- VERSION,
64
  ZEROCONF_MAP,
65
  )
66
 
67
 
68
- def _base_schema(
69
- discovery_info: ZeroconfServiceInfo | None,
70
- user_input: dict[str, Any] | None,
71
- ) -> vol.Schema:
72
  """Generate base schema for gateways."""
 
 
73
  if not discovery_info:
74
- if not user_input:
75
- return vol.Schema(
76
- {
77
- vol.Required(CONF_PASSWORD): str,
78
- vol.Required(CONF_HOST): str,
79
- vol.Optional(CONF_PORT, default=DEFAULT_PORT): int,
80
- vol.Required(CONF_USERNAME, default=SMILE): vol.In(
81
- {SMILE: FLOW_SMILE, STRETCH: FLOW_STRETCH}
82
- ),
83
- }
84
- )
85
- return vol.Schema(
86
  {
87
- vol.Required(CONF_PASSWORD, default=user_input[CONF_PASSWORD]): str,
88
- vol.Required(CONF_HOST, default=user_input[CONF_HOST]): str,
89
- vol.Optional(CONF_PORT, default=user_input[CONF_PORT]): int,
90
- vol.Required(CONF_USERNAME, default=user_input[CONF_USERNAME]): vol.In(
91
  {SMILE: FLOW_SMILE, STRETCH: FLOW_STRETCH}
92
  ),
93
  }
94
  )
95
 
96
- return vol.Schema({vol.Required(CONF_PASSWORD): str})
97
 
98
 
99
  async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> Smile:
100
  """Validate whether the user input allows us to connect to the gateway.
101
 
102
- Data has the keys from _base_schema() with values provided by the user.
103
  """
104
  websession = async_get_clientsession(hass, verify_ssl=False)
105
  api = Smile(
@@ -107,7 +71,6 @@
107
  password=data[CONF_PASSWORD],
108
  port=data[CONF_PORT],
109
  username=data[CONF_USERNAME],
110
- timeout=30,
111
  websession=websession,
112
  )
113
  await api.connect()
@@ -120,6 +83,7 @@
120
  VERSION = 1
121
 
122
  discovery_info: ZeroconfServiceInfo | None = None
 
123
  _username: str = DEFAULT_USERNAME
124
 
125
  async def async_step_zeroconf(
@@ -153,163 +117,79 @@
153
 
154
  if DEFAULT_USERNAME not in unique_id:
155
  self._username = STRETCH_USERNAME
156
- _product = _properties.get(PRODUCT, None)
157
- _version = _properties.get(VERSION, "n/a")
158
  _name = f"{ZEROCONF_MAP.get(_product, _product)} v{_version}"
159
 
160
  # This is an Anna, but we already have config entries.
161
  # Assuming that the user has already configured Adam, aborting discovery.
162
- if self._async_current_entries() and _product == SMILE_THERMO:
163
- return self.async_abort(reason=ANNA_WITH_ADAM)
164
 
165
  # If we have discovered an Adam or Anna, both might be on the network.
166
  # In that case, we need to cancel the Anna flow, as the Adam should
167
  # be added.
168
- for flow in self._async_in_progress():
169
- # This is an Anna, and there is already an Adam flow in progress
170
- if (
171
- _product == SMILE_THERMO
172
- and CONTEXT in flow
173
- and flow[CONTEXT].get(PRODUCT) == SMILE_OPEN_THERM
174
- ):
175
- return self.async_abort(reason=ANNA_WITH_ADAM)
176
-
177
- # This is an Adam, and there is already an Anna flow in progress
178
- if (
179
- _product == SMILE_OPEN_THERM
180
- and CONTEXT in flow
181
- and flow[CONTEXT].get(PRODUCT) == SMILE_THERMO
182
- and FLOW_ID in flow
183
- ):
184
- self.hass.config_entries.flow.async_abort(flow[FLOW_ID])
185
 
186
  self.context.update(
187
  {
188
- TITLE_PLACEHOLDERS: {
189
- CONF_HOST: discovery_info.host,
190
- CONF_NAME: _name,
191
- CONF_PORT: discovery_info.port,
192
- CONF_USERNAME: self._username,
193
- },
194
  ATTR_CONFIGURATION_URL: (
195
  f"http://{discovery_info.host}:{discovery_info.port}"
196
  ),
197
- PRODUCT: _product,
198
  }
199
  )
200
  return await self.async_step_user()
201
 
 
 
 
 
 
 
 
 
 
 
 
 
202
  async def async_step_user(
203
  self, user_input: dict[str, Any] | None = None
204
  ) -> ConfigFlowResult:
205
  """Handle the initial step when using network/gateway setups."""
206
  errors: dict[str, str] = {}
207
 
208
- if not user_input:
209
- return self.async_show_form(
210
- step_id=SOURCE_USER,
211
- data_schema=_base_schema(self.discovery_info, None),
212
- errors=errors,
213
- )
214
-
215
- if self.discovery_info:
216
- user_input[CONF_HOST] = self.discovery_info.host
217
- user_input[CONF_PORT] = self.discovery_info.port
218
- user_input[CONF_USERNAME] = self._username
219
- try:
220
- api = await validate_input(self.hass, user_input)
221
- except ConnectionFailedError:
222
- errors[CONF_BASE] = "cannot_connect"
223
- except InvalidAuthentication:
224
- errors[CONF_BASE] = "invalid_auth"
225
- except InvalidSetupError:
226
- errors[CONF_BASE] = "invalid_setup"
227
- except (InvalidXMLError, ResponseError):
228
- errors[CONF_BASE] = "response_error"
229
- except UnsupportedDeviceError:
230
- errors[CONF_BASE] = "unsupported"
231
- except Exception: # noqa: BLE001
232
- errors[CONF_BASE] = "unknown"
233
-
234
- if errors:
235
- return self.async_show_form(
236
- step_id=SOURCE_USER,
237
- data_schema=_base_schema(None, user_input),
238
- errors=errors,
239
- )
240
-
241
- await self.async_set_unique_id(
242
- api.smile_hostname or api.gateway_id, raise_on_progress=False
243
- )
244
- self._abort_if_unique_id_configured()
245
-
246
- return self.async_create_entry(title=api.smile_name, data=user_input)
247
-
248
- @staticmethod
249
- @callback
250
- def async_get_options_flow(
251
- config_entry: ConfigEntry,
252
- ) -> OptionsFlow: # pw-beta options
253
- """Get the options flow for this handler."""
254
- return PlugwiseOptionsFlowHandler(config_entry)
255
-
256
-
257
- # pw-beta - change the scan-interval via CONFIGURE
258
- # pw-beta - add homekit emulation via CONFIGURE
259
- # pw-beta - change the frontend refresh interval via CONFIGURE
260
- class PlugwiseOptionsFlowHandler(OptionsFlowWithConfigEntry): # pw-beta options
261
- """Plugwise option flow."""
262
-
263
- async def async_step_none(
264
- self, user_input: dict[str, Any] | None = None
265
- ) -> ConfigFlowResult: # pragma: no cover
266
- """No options available."""
267
- if user_input is not None:
268
- # Apparently not possible to abort an options flow at the moment
269
- return self.async_create_entry(title="", data=self._options)
270
-
271
- return self.async_show_form(step_id="none")
272
-
273
- async def async_step_init(
274
- self, user_input: dict[str, Any] | None = None
275
- ) -> ConfigFlowResult: # pragma: no cover
276
- """Manage the Plugwise options."""
277
- if not self.config_entry.data.get(CONF_HOST):
278
- return await self.async_step_none(user_input)
279
-
280
  if user_input is not None:
281
- return self.async_create_entry(title="", data=user_input)
 
 
 
282
 
283
- coordinator = self.hass.data[DOMAIN][self.config_entry.entry_id][COORDINATOR]
284
- interval: dt.timedelta = DEFAULT_SCAN_INTERVAL[
285
- coordinator.api.smile_type
286
- ] # pw-beta options
287
-
288
- data = {
289
- vol.Optional(
290
- CONF_SCAN_INTERVAL,
291
- default=self._options.get(
292
- CONF_SCAN_INTERVAL, interval.seconds
293
- ),
294
- ): vol.All(cv.positive_int, vol.Clamp(min=10)),
295
- } # pw-beta
296
-
297
- if coordinator.api.smile_type != THERMOSTAT:
298
- return self.async_show_form(step_id=INIT, data_schema=vol.Schema(data))
 
 
 
299
 
300
- data.update(
301
- {
302
- vol.Optional(
303
- CONF_HOMEKIT_EMULATION,
304
- default=self._options.get(
305
- CONF_HOMEKIT_EMULATION, False
306
- ),
307
- ): cv.boolean,
308
- vol.Optional(
309
- CONF_REFRESH_INTERVAL,
310
- default=self._options.get(CONF_REFRESH_INTERVAL, 1.5),
311
- ): vol.All(vol.Coerce(float), vol.Range(min=1.5, max=10.0)),
312
- }
313
- ) # pw-beta
314
 
315
- return self.async_show_form(step_id=INIT, data_schema=vol.Schema(data))
 
 
 
 
 
1
  """Config flow for Plugwise integration."""
2
+
3
  from __future__ import annotations
4
 
5
+ from typing import Any, Self
 
6
 
7
  from plugwise import Smile
8
  from plugwise.exceptions import (
 
16
  import voluptuous as vol
17
 
18
  from homeassistant.components.zeroconf import ZeroconfServiceInfo
19
+ from homeassistant.config_entries import SOURCE_USER, ConfigFlow, ConfigFlowResult
 
 
 
 
 
 
 
20
  from homeassistant.const import (
21
  ATTR_CONFIGURATION_URL,
22
  CONF_BASE,
 
24
  CONF_NAME,
25
  CONF_PASSWORD,
26
  CONF_PORT,
 
27
  CONF_USERNAME,
28
  )
29
+ from homeassistant.core import HomeAssistant
 
30
  from homeassistant.helpers.aiohttp_client import async_get_clientsession
31
 
32
  from .const import (
 
 
 
 
 
33
  DEFAULT_PORT,
 
34
  DEFAULT_USERNAME,
35
  DOMAIN,
 
36
  FLOW_SMILE,
37
  FLOW_STRETCH,
 
 
38
  SMILE,
 
 
39
  STRETCH,
40
  STRETCH_USERNAME,
 
 
 
41
  ZEROCONF_MAP,
42
  )
43
 
44
 
45
+ def base_schema(discovery_info: ZeroconfServiceInfo | None) -> vol.Schema:
 
 
 
46
  """Generate base schema for gateways."""
47
+ schema = vol.Schema({vol.Required(CONF_PASSWORD): str})
48
+
49
  if not discovery_info:
50
+ schema = schema.extend(
 
 
 
 
 
 
 
 
 
 
 
51
  {
52
+ vol.Required(CONF_HOST): str,
53
+ vol.Optional(CONF_PORT, default=DEFAULT_PORT): int,
54
+ vol.Required(CONF_USERNAME, default=SMILE): vol.In(
 
55
  {SMILE: FLOW_SMILE, STRETCH: FLOW_STRETCH}
56
  ),
57
  }
58
  )
59
 
60
+ return schema
61
 
62
 
63
  async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> Smile:
64
  """Validate whether the user input allows us to connect to the gateway.
65
 
66
+ Data has the keys from base_schema() with values provided by the user.
67
  """
68
  websession = async_get_clientsession(hass, verify_ssl=False)
69
  api = Smile(
 
71
  password=data[CONF_PASSWORD],
72
  port=data[CONF_PORT],
73
  username=data[CONF_USERNAME],
 
74
  websession=websession,
75
  )
76
  await api.connect()
 
83
  VERSION = 1
84
 
85
  discovery_info: ZeroconfServiceInfo | None = None
86
+ product: str = "Unknown Smile"
87
  _username: str = DEFAULT_USERNAME
88
 
89
  async def async_step_zeroconf(
 
117
 
118
  if DEFAULT_USERNAME not in unique_id:
119
  self._username = STRETCH_USERNAME
120
+ self.product = _product = _properties.get("product", "Unknown Smile")
121
+ _version = _properties.get("version", "n/a")
122
  _name = f"{ZEROCONF_MAP.get(_product, _product)} v{_version}"
123
 
124
  # This is an Anna, but we already have config entries.
125
  # Assuming that the user has already configured Adam, aborting discovery.
126
+ if self._async_current_entries() and _product == "smile_thermo":
127
+ return self.async_abort(reason="anna_with_adam")
128
 
129
  # If we have discovered an Adam or Anna, both might be on the network.
130
  # In that case, we need to cancel the Anna flow, as the Adam should
131
  # be added.
132
+ if self.hass.config_entries.flow.async_has_matching_flow(self):
133
+ return self.async_abort(reason="anna_with_adam")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
134
 
135
  self.context.update(
136
  {
137
+ "title_placeholders": {CONF_NAME: _name},
 
 
 
 
 
138
  ATTR_CONFIGURATION_URL: (
139
  f"http://{discovery_info.host}:{discovery_info.port}"
140
  ),
 
141
  }
142
  )
143
  return await self.async_step_user()
144
 
145
+ def is_matching(self, other_flow: Self) -> bool:
146
+ """Return True if other_flow is matching this flow."""
147
+ # This is an Anna, and there is already an Adam flow in progress
148
+ if self.product == "smile_thermo" and other_flow.product == "smile_open_therm":
149
+ return True
150
+
151
+ # This is an Adam, and there is already an Anna flow in progress
152
+ if self.product == "smile_open_therm" and other_flow.product == "smile_thermo":
153
+ self.hass.config_entries.flow.async_abort(other_flow.flow_id)
154
+
155
+ return False
156
+
157
  async def async_step_user(
158
  self, user_input: dict[str, Any] | None = None
159
  ) -> ConfigFlowResult:
160
  """Handle the initial step when using network/gateway setups."""
161
  errors: dict[str, str] = {}
162
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
163
  if user_input is not None:
164
+ if self.discovery_info:
165
+ user_input[CONF_HOST] = self.discovery_info.host
166
+ user_input[CONF_PORT] = self.discovery_info.port
167
+ user_input[CONF_USERNAME] = self._username
168
 
169
+ try:
170
+ api = await validate_input(self.hass, user_input)
171
+ except ConnectionFailedError:
172
+ errors[CONF_BASE] = "cannot_connect"
173
+ except InvalidAuthentication:
174
+ errors[CONF_BASE] = "invalid_auth"
175
+ except InvalidSetupError:
176
+ errors[CONF_BASE] = "invalid_setup"
177
+ except (InvalidXMLError, ResponseError):
178
+ errors[CONF_BASE] = "response_error"
179
+ except UnsupportedDeviceError:
180
+ errors[CONF_BASE] = "unsupported"
181
+ except Exception: # noqa: BLE001
182
+ errors[CONF_BASE] = "unknown"
183
+ else:
184
+ await self.async_set_unique_id(
185
+ api.smile_hostname or api.gateway_id, raise_on_progress=False
186
+ )
187
+ self._abort_if_unique_id_configured()
188
 
189
+ return self.async_create_entry(title=api.smile_name, data=user_input)
 
 
 
 
 
 
 
 
 
 
 
 
 
190
 
191
+ return self.async_show_form(
192
+ step_id=SOURCE_USER,
193
+ data_schema=base_schema(self.discovery_info),
194
+ errors=errors,
195
+ )
/home/runner/work/progress/progress/clones/beta/{beta/custom_components → ha-core/homeassistant/components}/plugwise/const.py RENAMED
@@ -1,4 +1,7 @@
1
  """Constants for Plugwise component."""
 
 
 
2
  from datetime import timedelta
3
  import logging
4
  from typing import Final, Literal
@@ -10,154 +13,18 @@
10
  LOGGER = logging.getLogger(__package__)
11
 
12
  API: Final = "api"
13
- COORDINATOR: Final = "coordinator"
14
- CONF_HOMEKIT_EMULATION: Final = "homekit_emulation" # pw-beta options
15
- CONF_REFRESH_INTERVAL: Final = "refresh_interval" # pw-beta options
16
- CONF_MANUAL_PATH: Final = "Enter Manually"
17
- DEVICES: Final = "devices"
18
  GATEWAY: Final = "gateway"
 
19
  LOCATION: Final = "location"
20
- MAC_ADDRESS: Final = "mac_address"
21
  REBOOT: Final = "reboot"
22
  SMILE: Final = "smile"
23
  STRETCH: Final = "stretch"
24
  STRETCH_USERNAME: Final = "stretch"
25
- UNIQUE_IDS: Final = "unique_ids"
26
- ZIGBEE_MAC_ADDRESS: Final = "zigbee_mac_address"
27
-
28
- # Binary Sensor constants
29
- BINARY_SENSORS: Final = "binary_sensors"
30
- COMPRESSOR_STATE: Final = "compressor_state"
31
- COOLING_ENABLED: Final = "cooling_enabled"
32
- COOLING_STATE: Final = "cooling_state"
33
- DHW_STATE: Final = "dhw_state"
34
- FLAME_STATE: Final = "flame_state"
35
- HEATING_STATE: Final = "heating_state"
36
- NOTIFICATIONS: Final ="notifications"
37
- PLUGWISE_NOTIFICATION: Final = "plugwise_notification"
38
- SECONDARY_BOILER_STATE: Final = "secondary_boiler_state"
39
-
40
- # Climate constants
41
- ACTIVE_PRESET: Final = "active_preset"
42
- CONTROL_STATE: Final = "control_state"
43
- COOLING_PRESENT: Final ="cooling_present"
44
- DEV_CLASS: Final = "dev_class"
45
- NONE : Final = "None"
46
- MODE: Final = "mode"
47
- TARGET_TEMP: Final = "setpoint"
48
- TARGET_TEMP_HIGH: Final = "setpoint_high"
49
- TARGET_TEMP_LOW: Final = "setpoint_low"
50
- THERMOSTAT: Final = "thermostat"
51
-
52
- # Config_flow constants
53
- ANNA_WITH_ADAM: Final = "anna_with_adam"
54
- CONTEXT: Final = "context"
55
- FLOW_ID: Final = "flow_id"
56
- FLOW_NET: Final = "Network: Smile/Stretch"
57
- FLOW_SMILE: Final = "Smile (Adam/Anna/P1)"
58
- FLOW_STRETCH: Final = "Stretch (Stretch)"
59
- FLOW_TYPE: Final = "flow_type"
60
- INIT: Final = "init"
61
- PRODUCT: Final = "product"
62
- SMILE_OPEN_THERM: Final = "smile_open_therm"
63
- SMILE_THERMO: Final = "smile_thermo"
64
- TITLE_PLACEHOLDERS: Final = "title_placeholders"
65
- VERSION: Final = "version"
66
-
67
- # Entity constants
68
- AVAILABLE: Final = "available"
69
- FIRMWARE: Final = "firmware"
70
- GATEWAY_ID: Final = "gateway_id"
71
- HARDWARE: Final = "hardware"
72
- MODEL: Final = "model"
73
- SMILE_NAME: Final = "smile_name"
74
- VENDOR: Final = "vendor"
75
-
76
- # Number constants
77
- MAX_BOILER_TEMP: Final = "maximum_boiler_temperature"
78
- MAX_DHW_TEMP: Final = "max_dhw_temperature"
79
- LOWER_BOUND: Final = "lower_bound"
80
- RESOLUTION: Final = "resolution"
81
- TEMPERATURE_OFFSET: Final = "temperature_offset"
82
- UPPER_BOUND: Final = "upper_bound"
83
-
84
- # Sensor constants
85
- DHW_TEMP: Final = "dhw_temperature"
86
- DHW_SETPOINT: Final = "domestic_hot_water_setpoint"
87
- EL_CONSUMED: Final = "electricity_consumed"
88
- EL_CONS_INTERVAL: Final = "electricity_consumed_interval"
89
- EL_CONS_OP_CUMULATIVE: Final = "electricity_consumed_off_peak_cumulative"
90
- EL_CONS_OP_INTERVAL: Final = "electricity_consumed_off_peak_interval"
91
- EL_CONS_OP_POINT: Final = "electricity_consumed_off_peak_point"
92
- EL_CONS_P_CUMULATIVE: Final = "electricity_consumed_peak_cumulative"
93
- EL_CONS_P_INTERVAL: Final = "electricity_consumed_peak_interval"
94
- EL_CONS_P_POINT: Final = "electricity_consumed_peak_point"
95
- EL_CONS_POINT: Final = "electricity_consumed_point"
96
- EL_PH1_CONSUMED: Final = "electricity_phase_one_consumed"
97
- EL_PH2_CONSUMED: Final = "electricity_phase_two_consumed"
98
- EL_PH3_CONSUMED: Final = "electricity_phase_three_consumed"
99
- EL_PH1_PRODUCED: Final = "electricity_phase_one_produced"
100
- EL_PH2_PRODUCED: Final = "electricity_phase_two_produced"
101
- EL_PH3_PRODUCED: Final = "electricity_phase_three_produced"
102
- EL_PRODUCED: Final = "electricity_produced"
103
- EL_PROD_INTERVAL: Final = "electricity_produced_interval"
104
- EL_PROD_OP_CUMULATIVE: Final = "electricity_produced_off_peak_cumulative"
105
- EL_PROD_OP_INTERVAL: Final = "electricity_produced_off_peak_interval"
106
- EL_PROD_OP_POINT: Final = "electricity_produced_off_peak_point"
107
- EL_PROD_P_CUMULATIVE: Final = "electricity_produced_peak_cumulative"
108
- EL_PROD_P_INTERVAL: Final = "electricity_produced_peak_interval"
109
- EL_PROD_P_POINT: Final = "electricity_produced_peak_point"
110
- EL_PROD_POINT: Final = "electricity_produced_point"
111
- GAS_CONS_CUMULATIVE: Final = "gas_consumed_cumulative"
112
- GAS_CONS_INTERVAL: Final = "gas_consumed_interval"
113
- INTENDED_BOILER_TEMP: Final = "intended_boiler_temperature"
114
- MOD_LEVEL: Final = "modulation_level"
115
- NET_EL_POINT: Final = "net_electricity_point"
116
- NET_EL_CUMULATIVE: Final = "net_electricity_cumulative"
117
- OUTDOOR_AIR_TEMP: Final = "outdoor_air_temperature"
118
- OUTDOOR_TEMP: Final = "outdoor_temperature"
119
- RETURN_TEMP: Final = "return_temperature"
120
- SENSORS: Final = "sensors"
121
- TEMP_DIFF: Final = "temperature_difference"
122
- VALVE_POS: Final = "valve_position"
123
- VOLTAGE_PH1: Final = "voltage_phase_one"
124
- VOLTAGE_PH2: Final = "voltage_phase_two"
125
- VOLTAGE_PH3: Final = "voltage_phase_three"
126
- WATER_TEMP: Final = "water_temperature"
127
- WATER_PRESSURE: Final = "water_pressure"
128
-
129
- # Select constants
130
- AVAILABLE_SCHEDULES: Final = "available_schedules"
131
- DHW_MODE: Final = "dhw_mode"
132
- DHW_MODES: Final = "dhw_modes"
133
- GATEWAY_MODE: Final = "gateway_mode"
134
- GATEWAY_MODES: Final = "gateway_modes"
135
- REGULATION_MODE: Final = "regulation_mode"
136
- REGULATION_MODES: Final = "regulation_modes"
137
- SELECT_DHW_MODE: Final = "select_dhw_mode"
138
- SELECT_GATEWAY_MODE: Final = "select_gateway_mode"
139
- SELECT_REGULATION_MODE: Final = "select_regulation_mode"
140
- SELECT_SCHEDULE: Final = "select_schedule"
141
-
142
- # Switch constants
143
- DHW_CM_SWITCH: Final = "dhw_cm_switch"
144
- LOCK: Final = "lock"
145
- MEMBERS: Final ="members"
146
- RELAY: Final = "relay"
147
- COOLING_ENA_SWITCH: Final ="cooling_ena_switch"
148
- SWITCHES: Final = "switches"
149
 
150
- # Default directives
151
- DEFAULT_PORT: Final[int] = 80
152
- DEFAULT_SCAN_INTERVAL: Final[dict[str, timedelta]] = {
153
- "power": timedelta(seconds=10),
154
- "stretch": timedelta(seconds=60),
155
- "thermostat": timedelta(seconds=60),
156
- }
157
- DEFAULT_TIMEOUT: Final[int] = 10
158
- DEFAULT_USERNAME: Final = "smile"
159
-
160
- # --- Const for Plugwise Smile and Stretch
161
  PLATFORMS: Final[list[str]] = [
162
  Platform.BINARY_SENSOR,
163
  Platform.BUTTON,
@@ -167,18 +34,6 @@
167
  Platform.SENSOR,
168
  Platform.SWITCH,
169
  ]
170
- SERVICE_DELETE: Final = "delete_notification"
171
- SEVERITIES: Final[list[str]] = ["other", "info", "message", "warning", "error"]
172
-
173
- # Climate const:
174
- MASTER_THERMOSTATS: Final[list[str]] = [
175
- "thermostat",
176
- "zone_thermometer",
177
- "zone_thermostat",
178
- "thermostatic_radiator_valve",
179
- ]
180
-
181
- # Config_flow const:
182
  ZEROCONF_MAP: Final[dict[str, str]] = {
183
  "smile": "Smile P1",
184
  "smile_thermo": "Smile Anna",
@@ -186,21 +41,39 @@
186
  "stretch": "Stretch",
187
  }
188
 
189
- NumberType = Literal[
190
  "maximum_boiler_temperature",
191
  "max_dhw_temperature",
192
  "temperature_offset",
193
  ]
194
 
195
- SelectType = Literal[
196
  "select_dhw_mode",
197
  "select_gateway_mode",
198
  "select_regulation_mode",
199
  "select_schedule",
200
  ]
201
- SelectOptionsType = Literal[
202
  "dhw_modes",
203
  "gateway_modes",
204
  "regulation_modes",
205
  "available_schedules",
206
  ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  """Constants for Plugwise component."""
2
+
3
+ from __future__ import annotations
4
+
5
  from datetime import timedelta
6
  import logging
7
  from typing import Final, Literal
 
13
  LOGGER = logging.getLogger(__package__)
14
 
15
  API: Final = "api"
16
+ FLOW_SMILE: Final = "smile (Adam/Anna/P1)"
17
+ FLOW_STRETCH: Final = "stretch (Stretch)"
18
+ FLOW_TYPE: Final = "flow_type"
 
 
19
  GATEWAY: Final = "gateway"
20
+ GATEWAY_ID: Final = "gateway_id"
21
  LOCATION: Final = "location"
22
+ PW_TYPE: Final = "plugwise_type"
23
  REBOOT: Final = "reboot"
24
  SMILE: Final = "smile"
25
  STRETCH: Final = "stretch"
26
  STRETCH_USERNAME: Final = "stretch"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
 
 
 
 
 
 
 
 
 
 
 
 
28
  PLATFORMS: Final[list[str]] = [
29
  Platform.BINARY_SENSOR,
30
  Platform.BUTTON,
 
34
  Platform.SENSOR,
35
  Platform.SWITCH,
36
  ]
 
 
 
 
 
 
 
 
 
 
 
 
37
  ZEROCONF_MAP: Final[dict[str, str]] = {
38
  "smile": "Smile P1",
39
  "smile_thermo": "Smile Anna",
 
41
  "stretch": "Stretch",
42
  }
43
 
44
+ type NumberType = Literal[
45
  "maximum_boiler_temperature",
46
  "max_dhw_temperature",
47
  "temperature_offset",
48
  ]
49
 
50
+ type SelectType = Literal[
51
  "select_dhw_mode",
52
  "select_gateway_mode",
53
  "select_regulation_mode",
54
  "select_schedule",
55
  ]
56
+ type SelectOptionsType = Literal[
57
  "dhw_modes",
58
  "gateway_modes",
59
  "regulation_modes",
60
  "available_schedules",
61
  ]
62
+
63
+ # Default directives
64
+ DEFAULT_MAX_TEMP: Final = 30
65
+ DEFAULT_MIN_TEMP: Final = 4
66
+ DEFAULT_PORT: Final = 80
67
+ DEFAULT_SCAN_INTERVAL: Final[dict[str, timedelta]] = {
68
+ "power": timedelta(seconds=10),
69
+ "stretch": timedelta(seconds=60),
70
+ "thermostat": timedelta(seconds=60),
71
+ }
72
+ DEFAULT_USERNAME: Final = "smile"
73
+
74
+ MASTER_THERMOSTATS: Final[list[str]] = [
75
+ "thermostat",
76
+ "thermostatic_radiator_valve",
77
+ "zone_thermometer",
78
+ "zone_thermostat",
79
+ ]
/home/runner/work/progress/progress/clones/beta/{beta/custom_components → ha-core/homeassistant/components}/plugwise/coordinator.py RENAMED
@@ -1,8 +1,8 @@
1
  """DataUpdateCoordinator for Plugwise."""
2
 
3
- from collections.abc import Callable
4
  from datetime import timedelta
5
 
 
6
  from plugwise import PlugwiseData, Smile
7
  from plugwise.exceptions import (
8
  ConnectionFailedError,
@@ -14,13 +14,7 @@
14
  )
15
 
16
  from homeassistant.config_entries import ConfigEntry
17
- from homeassistant.const import (
18
- CONF_HOST,
19
- CONF_PASSWORD,
20
- CONF_PORT,
21
- CONF_SCAN_INTERVAL, # pw-beta options
22
- CONF_USERNAME,
23
- )
24
  from homeassistant.core import HomeAssistant
25
  from homeassistant.exceptions import ConfigEntryError
26
  from homeassistant.helpers import device_registry as dr
@@ -28,14 +22,7 @@
28
  from homeassistant.helpers.debounce import Debouncer
29
  from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
30
 
31
- from .const import (
32
- DEFAULT_PORT,
33
- DEFAULT_SCAN_INTERVAL,
34
- DEFAULT_USERNAME,
35
- DOMAIN,
36
- GATEWAY_ID,
37
- LOGGER,
38
- )
39
 
40
 
41
  class PlugwiseDataUpdateCoordinator(DataUpdateCoordinator[PlugwiseData]):
@@ -45,25 +32,19 @@
45
 
46
  config_entry: ConfigEntry
47
 
48
- def __init__(
49
- self,
50
- hass: HomeAssistant,
51
- cooldown: float,
52
- update_interval: timedelta = timedelta(seconds=60),
53
- ) -> None: # pw-beta cooldown
54
  """Initialize the coordinator."""
55
  super().__init__(
56
  hass,
57
  LOGGER,
58
  name=DOMAIN,
59
- # Core directly updates from const's DEFAULT_SCAN_INTERVAL
60
- update_interval=update_interval,
61
  # Don't refresh immediately, give the device time to process
62
  # the change in state before we query it.
63
  request_refresh_debouncer=Debouncer(
64
  hass,
65
  LOGGER,
66
- cooldown=cooldown,
67
  immediate=False,
68
  ),
69
  )
@@ -73,28 +54,17 @@
73
  username=self.config_entry.data.get(CONF_USERNAME, DEFAULT_USERNAME),
74
  password=self.config_entry.data[CONF_PASSWORD],
75
  port=self.config_entry.data.get(CONF_PORT, DEFAULT_PORT),
76
- timeout=30,
77
  websession=async_get_clientsession(hass, verify_ssl=False),
78
  )
79
  self._current_devices: set[str] = set()
80
  self.new_devices: set[str] = set()
81
- self.new_devices_callbacks: list[Callable[[str], None]] = []
82
- self.update_interval = update_interval
83
 
84
  async def _connect(self) -> None:
85
  """Connect to the Plugwise Smile."""
86
- self._connected = await self.api.connect()
87
- self.api.get_all_devices()
88
-
89
- self.update_interval = DEFAULT_SCAN_INTERVAL.get(
90
- self.api.smile_type, timedelta(seconds=60)
91
- ) # pw-beta options scan-interval
92
- if (custom_time := self.config_entry.options.get(CONF_SCAN_INTERVAL)) is not None:
93
- self.update_interval = timedelta(
94
- seconds=int(custom_time)
95
- ) # pragma: no cover # pw-beta options
96
-
97
- LOGGER.debug("DUC update interval: %s", self.update_interval) # pw-beta options
98
 
99
  async def _async_update_data(self) -> PlugwiseData:
100
  """Fetch data from Plugwise."""
@@ -104,56 +74,52 @@
104
  await self._connect()
105
  data = await self.api.async_update()
106
  except ConnectionFailedError as err:
107
- raise UpdateFailed("Failed to connect") from err
108
  except InvalidAuthentication as err:
109
- raise ConfigEntryError("Authentication failed") from err
110
  except (InvalidXMLError, ResponseError) as err:
111
- raise UpdateFailed(
112
- "Invalid XML data, or error indication received from the Plugwise Adam/Smile/Stretch"
113
- ) from err
114
  except PlugwiseError as err:
115
- raise UpdateFailed("Data incomplete or missing") from err
116
  except UnsupportedDeviceError as err:
117
- raise ConfigEntryError("Device with unsupported firmware") from err
118
  else:
119
- LOGGER.debug(f"{self.api.smile_name} data: %s", data)
120
  self._async_add_remove_devices(data, self.config_entry)
121
 
122
  return data
123
 
124
- def _async_add_remove_devices(self, data:PlugwiseData, entry: ConfigEntry,) -> None:
125
  """Add new Plugwise devices, remove non-existing devices."""
126
  # Check for new or removed devices
127
- if new_devices := set(data.devices) - self._current_devices:
128
- self.new_devices = new_devices
129
- LOGGER.debug("New Plugwise devices found: %s", new_devices)
130
- for device_id in new_devices:
131
- for callback in self.new_devices_callbacks:
132
- callback(device_id)
133
-
134
  removed_devices = self._current_devices - set(data.devices)
135
  self._current_devices = set(data.devices)
136
- if not removed_devices:
137
- return
138
 
139
- # Clean device_registry when removed devices found
 
 
 
 
140
  device_reg = dr.async_get(self.hass)
141
  device_list = dr.async_entries_for_config_entry(
142
  device_reg, self.config_entry.entry_id
143
  )
144
- # via_device cannot be None, this will result in the deletion
145
- # of other Plugwise Gateways when present!
146
- via_device: str = ""
 
 
 
 
 
147
  for device_entry in device_list:
148
- if device_entry.identifiers:
149
- item = list(list(device_entry.identifiers)[0])
150
- if item[0] == DOMAIN:
151
- # First find the Plugwise via_device, this is always the first device
152
- if item[1] == data.gateway[GATEWAY_ID]:
153
- via_device = device_entry.id
154
- elif ( # then remove the connected orphaned device(s)
155
- device_entry.via_device_id == via_device
156
- and item[1] not in list(data.devices.keys())
157
  ):
158
  device_reg.async_update_device(
159
  device_entry.id, remove_config_entry_id=entry.entry_id
@@ -162,5 +128,5 @@
162
  "Removed %s device %s %s from device_registry",
163
  DOMAIN,
164
  device_entry.model,
165
- item[1],
166
  )
 
1
  """DataUpdateCoordinator for Plugwise."""
2
 
 
3
  from datetime import timedelta
4
 
5
+ from packaging.version import Version
6
  from plugwise import PlugwiseData, Smile
7
  from plugwise.exceptions import (
8
  ConnectionFailedError,
 
14
  )
15
 
16
  from homeassistant.config_entries import ConfigEntry
17
+ from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_USERNAME
 
 
 
 
 
 
18
  from homeassistant.core import HomeAssistant
19
  from homeassistant.exceptions import ConfigEntryError
20
  from homeassistant.helpers import device_registry as dr
 
22
  from homeassistant.helpers.debounce import Debouncer
23
  from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
24
 
25
+ from .const import DEFAULT_PORT, DEFAULT_USERNAME, DOMAIN, GATEWAY_ID, LOGGER
 
 
 
 
 
 
 
26
 
27
 
28
  class PlugwiseDataUpdateCoordinator(DataUpdateCoordinator[PlugwiseData]):
 
32
 
33
  config_entry: ConfigEntry
34
 
35
+ def __init__(self, hass: HomeAssistant) -> None:
 
 
 
 
 
36
  """Initialize the coordinator."""
37
  super().__init__(
38
  hass,
39
  LOGGER,
40
  name=DOMAIN,
41
+ update_interval=timedelta(seconds=60),
 
42
  # Don't refresh immediately, give the device time to process
43
  # the change in state before we query it.
44
  request_refresh_debouncer=Debouncer(
45
  hass,
46
  LOGGER,
47
+ cooldown=1.5,
48
  immediate=False,
49
  ),
50
  )
 
54
  username=self.config_entry.data.get(CONF_USERNAME, DEFAULT_USERNAME),
55
  password=self.config_entry.data[CONF_PASSWORD],
56
  port=self.config_entry.data.get(CONF_PORT, DEFAULT_PORT),
 
57
  websession=async_get_clientsession(hass, verify_ssl=False),
58
  )
59
  self._current_devices: set[str] = set()
60
  self.new_devices: set[str] = set()
 
 
61
 
62
  async def _connect(self) -> None:
63
  """Connect to the Plugwise Smile."""
64
+ version = await self.api.connect()
65
+ self._connected = isinstance(version, Version)
66
+ if self._connected:
67
+ self.api.get_all_devices()
 
 
 
 
 
 
 
 
68
 
69
  async def _async_update_data(self) -> PlugwiseData:
70
  """Fetch data from Plugwise."""
 
74
  await self._connect()
75
  data = await self.api.async_update()
76
  except ConnectionFailedError as err:
77
+ raise UpdateFailed("Failed to connect") from err
78
  except InvalidAuthentication as err:
79
+ raise ConfigEntryError("Authentication failed") from err
80
  except (InvalidXMLError, ResponseError) as err:
81
+ raise UpdateFailed(
82
+ "Invalid XML data, or error indication received from the Plugwise Adam/Smile/Stretch"
83
+ ) from err
84
  except PlugwiseError as err:
85
+ raise UpdateFailed("Data incomplete or missing") from err
86
  except UnsupportedDeviceError as err:
87
+ raise ConfigEntryError("Device with unsupported firmware") from err
88
  else:
 
89
  self._async_add_remove_devices(data, self.config_entry)
90
 
91
  return data
92
 
93
+ def _async_add_remove_devices(self, data: PlugwiseData, entry: ConfigEntry) -> None:
94
  """Add new Plugwise devices, remove non-existing devices."""
95
  # Check for new or removed devices
96
+ self.new_devices = set(data.devices) - self._current_devices
 
 
 
 
 
 
97
  removed_devices = self._current_devices - set(data.devices)
98
  self._current_devices = set(data.devices)
 
 
99
 
100
+ if removed_devices:
101
+ self._async_remove_devices(data, entry)
102
+
103
+ def _async_remove_devices(self, data: PlugwiseData, entry: ConfigEntry) -> None:
104
+ """Clean registries when removed devices found."""
105
  device_reg = dr.async_get(self.hass)
106
  device_list = dr.async_entries_for_config_entry(
107
  device_reg, self.config_entry.entry_id
108
  )
109
+ # First find the Plugwise via_device
110
+ gateway_device = device_reg.async_get_device(
111
+ {(DOMAIN, data.gateway[GATEWAY_ID])}
112
+ )
113
+ assert gateway_device is not None
114
+ via_device_id = gateway_device.id
115
+
116
+ # Then remove the connected orphaned device(s)
117
  for device_entry in device_list:
118
+ for identifier in device_entry.identifiers:
119
+ if identifier[0] == DOMAIN:
120
+ if (
121
+ device_entry.via_device_id == via_device_id
122
+ and identifier[1] not in data.devices
 
 
 
 
123
  ):
124
  device_reg.async_update_device(
125
  device_entry.id, remove_config_entry_id=entry.entry_id
 
128
  "Removed %s device %s %s from device_registry",
129
  DOMAIN,
130
  device_entry.model,
131
+ identifier[1],
132
  )
/home/runner/work/progress/progress/clones/beta/{beta/custom_components → ha-core/homeassistant/components}/plugwise/diagnostics.py RENAMED
@@ -1,4 +1,5 @@
1
  """Diagnostics support for Plugwise."""
 
2
  from __future__ import annotations
3
 
4
  from typing import Any
 
1
  """Diagnostics support for Plugwise."""
2
+
3
  from __future__ import annotations
4
 
5
  from typing import Any
/home/runner/work/progress/progress/clones/beta/{beta/custom_components → ha-core/homeassistant/components}/plugwise/entity.py RENAMED
@@ -1,4 +1,5 @@
1
  """Generic Plugwise Entity Class."""
 
2
  from __future__ import annotations
3
 
4
  from plugwise.constants import DeviceData
@@ -11,18 +12,7 @@
11
  )
12
  from homeassistant.helpers.update_coordinator import CoordinatorEntity
13
 
14
- from .const import (
15
- AVAILABLE,
16
- DOMAIN,
17
- FIRMWARE,
18
- GATEWAY_ID,
19
- HARDWARE,
20
- MAC_ADDRESS,
21
- MODEL,
22
- SMILE_NAME,
23
- VENDOR,
24
- ZIGBEE_MAC_ADDRESS,
25
- )
26
  from .coordinator import PlugwiseDataUpdateCoordinator
27
 
28
 
@@ -46,29 +36,30 @@
46
 
47
  data = coordinator.data.devices[device_id]
48
  connections = set()
49
- if mac := data.get(MAC_ADDRESS):
50
  connections.add((CONNECTION_NETWORK_MAC, mac))
51
- if mac := data.get(ZIGBEE_MAC_ADDRESS):
52
  connections.add((CONNECTION_ZIGBEE, mac))
53
 
54
  self._attr_device_info = DeviceInfo(
55
  configuration_url=configuration_url,
56
  identifiers={(DOMAIN, device_id)},
57
  connections=connections,
58
- manufacturer=data.get(VENDOR),
59
- model=data.get(MODEL),
60
- name=coordinator.data.gateway[SMILE_NAME],
61
- sw_version=data.get(FIRMWARE),
62
- hw_version=data.get(HARDWARE),
 
63
  )
64
 
65
- if device_id != coordinator.data.gateway[GATEWAY_ID]:
66
  self._attr_device_info.update(
67
  {
68
- ATTR_NAME: data.get(ATTR_NAME),
69
  ATTR_VIA_DEVICE: (
70
  DOMAIN,
71
- str(self.coordinator.data.gateway[GATEWAY_ID]),
72
  ),
73
  }
74
  )
@@ -78,9 +69,7 @@
78
  """Return if entity is available."""
79
  return (
80
  self._dev_id in self.coordinator.data.devices
81
- # Do not change the below line: some Plugwise devices
82
- # do not provide their availability-status!
83
- and (AVAILABLE not in self.device or self.device[AVAILABLE] is True)
84
  and super().available
85
  )
86
 
 
1
  """Generic Plugwise Entity Class."""
2
+
3
  from __future__ import annotations
4
 
5
  from plugwise.constants import DeviceData
 
12
  )
13
  from homeassistant.helpers.update_coordinator import CoordinatorEntity
14
 
15
+ from .const import DOMAIN
 
 
 
 
 
 
 
 
 
 
 
16
  from .coordinator import PlugwiseDataUpdateCoordinator
17
 
18
 
 
36
 
37
  data = coordinator.data.devices[device_id]
38
  connections = set()
39
+ if mac := data.get("mac_address"):
40
  connections.add((CONNECTION_NETWORK_MAC, mac))
41
+ if mac := data.get("zigbee_mac_address"):
42
  connections.add((CONNECTION_ZIGBEE, mac))
43
 
44
  self._attr_device_info = DeviceInfo(
45
  configuration_url=configuration_url,
46
  identifiers={(DOMAIN, device_id)},
47
  connections=connections,
48
+ manufacturer=data.get("vendor"),
49
+ model=data.get("model"),
50
+ model_id=data.get("model_id"),
51
+ name=coordinator.data.gateway["smile_name"],
52
+ sw_version=data.get("firmware"),
53
+ hw_version=data.get("hardware"),
54
  )
55
 
56
+ if device_id != coordinator.data.gateway["gateway_id"]:
57
  self._attr_device_info.update(
58
  {
59
+ ATTR_NAME: data.get("name"),
60
  ATTR_VIA_DEVICE: (
61
  DOMAIN,
62
+ str(self.coordinator.data.gateway["gateway_id"]),
63
  ),
64
  }
65
  )
 
69
  """Return if entity is available."""
70
  return (
71
  self._dev_id in self.coordinator.data.devices
72
+ and ("available" not in self.device or self.device["available"] is True)
 
 
73
  and super().available
74
  )
75
 
/home/runner/work/progress/progress/clones/beta/{beta/custom_components → ha-core/homeassistant/components}/plugwise/icons.json RENAMED
@@ -120,8 +120,5 @@
120
  "default": "mdi:lock"
121
  }
122
  }
123
- },
124
- "services": {
125
- "delete_notification": "mdi:trash-can"
126
  }
127
  }
 
120
  "default": "mdi:lock"
121
  }
122
  }
 
 
 
123
  }
124
  }
/home/runner/work/progress/progress/clones/beta/{beta/custom_components → ha-core/homeassistant/components}/plugwise/manifest.json RENAMED
@@ -1,13 +1,12 @@
1
  {
2
  "domain": "plugwise",
3
- "name": "Plugwise Smile/Stretch Beta",
4
- "after_dependencies": ["zeroconf"],
5
- "codeowners": ["@CoMPaTech", "@bouwew"],
6
  "config_flow": true,
7
- "documentation": "https://github.com/plugwise/plugwise-beta",
8
  "integration_type": "hub",
9
  "iot_class": "local_polling",
10
  "loggers": ["plugwise"],
11
- "requirements": ["plugwise==0.38.3"],
12
- "version": "0.51.3"
13
  }
 
1
  {
2
  "domain": "plugwise",
3
+ "name": "Plugwise",
4
+ "codeowners": ["@CoMPaTech", "@bouwew", "@frenck"],
 
5
  "config_flow": true,
6
+ "documentation": "https://www.home-assistant.io/integrations/plugwise",
7
  "integration_type": "hub",
8
  "iot_class": "local_polling",
9
  "loggers": ["plugwise"],
10
+ "requirements": ["plugwise==1.5.0"],
11
+ "zeroconf": ["_plugwise._tcp.local."]
12
  }
/home/runner/work/progress/progress/clones/beta/{beta/custom_components → ha-core/homeassistant/components}/plugwise/number.py RENAMED
@@ -1,4 +1,5 @@
1
  """Number platform for Plugwise integration."""
 
2
  from __future__ import annotations
3
 
4
  from dataclasses import dataclass
@@ -9,21 +10,12 @@
9
  NumberEntityDescription,
10
  NumberMode,
11
  )
12
- from homeassistant.const import ATTR_NAME, EntityCategory, UnitOfTemperature
13
  from homeassistant.core import HomeAssistant, callback
14
  from homeassistant.helpers.entity_platform import AddEntitiesCallback
15
 
16
  from . import PlugwiseConfigEntry
17
- from .const import (
18
- LOGGER,
19
- LOWER_BOUND,
20
- MAX_BOILER_TEMP,
21
- MAX_DHW_TEMP,
22
- RESOLUTION,
23
- TEMPERATURE_OFFSET,
24
- UPPER_BOUND,
25
- NumberType,
26
- )
27
  from .coordinator import PlugwiseDataUpdateCoordinator
28
  from .entity import PlugwiseEntity
29
  from .util import plugwise_command
@@ -38,22 +30,22 @@
38
 
39
  NUMBER_TYPES = (
40
  PlugwiseNumberEntityDescription(
41
- key=MAX_BOILER_TEMP,
42
- translation_key=MAX_BOILER_TEMP,
43
  device_class=NumberDeviceClass.TEMPERATURE,
44
  entity_category=EntityCategory.CONFIG,
45
  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
46
  ),
47
  PlugwiseNumberEntityDescription(
48
- key=MAX_DHW_TEMP,
49
- translation_key=MAX_DHW_TEMP,
50
  device_class=NumberDeviceClass.TEMPERATURE,
51
  entity_category=EntityCategory.CONFIG,
52
  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
53
  ),
54
  PlugwiseNumberEntityDescription(
55
- key=TEMPERATURE_OFFSET,
56
- translation_key=TEMPERATURE_OFFSET,
57
  device_class=NumberDeviceClass.TEMPERATURE,
58
  entity_category=EntityCategory.CONFIG,
59
  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
@@ -66,7 +58,7 @@
66
  entry: PlugwiseConfigEntry,
67
  async_add_entities: AddEntitiesCallback,
68
  ) -> None:
69
- """Set up the Plugwise numbers from a ConfigEntry."""
70
  coordinator = entry.runtime_data
71
 
72
  @callback
@@ -75,22 +67,15 @@
75
  if not coordinator.new_devices:
76
  return
77
 
78
- entities: list[PlugwiseNumberEntity] = []
79
- for device_id, device in coordinator.data.devices.items():
80
- for description in NUMBER_TYPES:
81
- if description.key in device:
82
- entities.append(
83
- PlugwiseNumberEntity(coordinator, device_id, description)
84
- )
85
- LOGGER.debug(
86
- "Add %s %s number", device[ATTR_NAME], description.translation_key
87
- )
88
-
89
- async_add_entities(entities)
90
-
91
- entry.async_on_unload(coordinator.async_add_listener(_add_entities))
92
 
93
  _add_entities()
 
94
 
95
 
96
  class PlugwiseNumberEntity(PlugwiseEntity, NumberEntity):
@@ -106,16 +91,15 @@
106
  ) -> None:
107
  """Initiate Plugwise Number."""
108
  super().__init__(coordinator, device_id)
109
- self.actuator = self.device[description.key]
110
  self.device_id = device_id
111
  self.entity_description = description
112
  self._attr_unique_id = f"{device_id}-{description.key}"
113
  self._attr_mode = NumberMode.BOX
114
- self._attr_native_max_value = self.device[description.key][UPPER_BOUND]
115
- self._attr_native_min_value = self.device[description.key][LOWER_BOUND]
116
 
117
- native_step = self.device[description.key][RESOLUTION]
118
- if description.key != TEMPERATURE_OFFSET:
119
  native_step = max(native_step, 0.5)
120
  self._attr_native_step = native_step
121
 
@@ -127,7 +111,6 @@
127
  @plugwise_command
128
  async def async_set_native_value(self, value: float) -> None:
129
  """Change to the new setpoint value."""
130
- await self.coordinator.api.set_number(self.device_id, self.entity_description.key, value)
131
- LOGGER.debug(
132
- "Setting %s to %s was successful", self.entity_description.key, value
133
  )
 
1
  """Number platform for Plugwise integration."""
2
+
3
  from __future__ import annotations
4
 
5
  from dataclasses import dataclass
 
10
  NumberEntityDescription,
11
  NumberMode,
12
  )
13
+ from homeassistant.const import EntityCategory, UnitOfTemperature
14
  from homeassistant.core import HomeAssistant, callback
15
  from homeassistant.helpers.entity_platform import AddEntitiesCallback
16
 
17
  from . import PlugwiseConfigEntry
18
+ from .const import NumberType
 
 
 
 
 
 
 
 
 
19
  from .coordinator import PlugwiseDataUpdateCoordinator
20
  from .entity import PlugwiseEntity
21
  from .util import plugwise_command
 
30
 
31
  NUMBER_TYPES = (
32
  PlugwiseNumberEntityDescription(
33
+ key="maximum_boiler_temperature",
34
+ translation_key="maximum_boiler_temperature",
35
  device_class=NumberDeviceClass.TEMPERATURE,
36
  entity_category=EntityCategory.CONFIG,
37
  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
38
  ),
39
  PlugwiseNumberEntityDescription(
40
+ key="max_dhw_temperature",
41
+ translation_key="max_dhw_temperature",
42
  device_class=NumberDeviceClass.TEMPERATURE,
43
  entity_category=EntityCategory.CONFIG,
44
  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
45
  ),
46
  PlugwiseNumberEntityDescription(
47
+ key="temperature_offset",
48
+ translation_key="temperature_offset",
49
  device_class=NumberDeviceClass.TEMPERATURE,
50
  entity_category=EntityCategory.CONFIG,
51
  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
 
58
  entry: PlugwiseConfigEntry,
59
  async_add_entities: AddEntitiesCallback,
60
  ) -> None:
61
+ """Set up Plugwise number platform."""
62
  coordinator = entry.runtime_data
63
 
64
  @callback
 
67
  if not coordinator.new_devices:
68
  return
69
 
70
+ async_add_entities(
71
+ PlugwiseNumberEntity(coordinator, device_id, description)
72
+ for device_id in coordinator.new_devices
73
+ for description in NUMBER_TYPES
74
+ if description.key in coordinator.data.devices[device_id]
75
+ )
 
 
 
 
 
 
 
 
76
 
77
  _add_entities()
78
+ entry.async_on_unload(coordinator.async_add_listener(_add_entities))
79
 
80
 
81
  class PlugwiseNumberEntity(PlugwiseEntity, NumberEntity):
 
91
  ) -> None:
92
  """Initiate Plugwise Number."""
93
  super().__init__(coordinator, device_id)
 
94
  self.device_id = device_id
95
  self.entity_description = description
96
  self._attr_unique_id = f"{device_id}-{description.key}"
97
  self._attr_mode = NumberMode.BOX
98
+ self._attr_native_max_value = self.device[description.key]["upper_bound"]
99
+ self._attr_native_min_value = self.device[description.key]["lower_bound"]
100
 
101
+ native_step = self.device[description.key]["resolution"]
102
+ if description.key != "temperature_offset":
103
  native_step = max(native_step, 0.5)
104
  self._attr_native_step = native_step
105
 
 
111
  @plugwise_command
112
  async def async_set_native_value(self, value: float) -> None:
113
  """Change to the new setpoint value."""
114
+ await self.coordinator.api.set_number(
115
+ self.device_id, self.entity_description.key, value
 
116
  )
/home/runner/work/progress/progress/clones/beta/{beta/custom_components → ha-core/homeassistant/components}/plugwise/select.py RENAMED
@@ -1,37 +1,20 @@
1
  """Plugwise Select component for Home Assistant."""
 
2
  from __future__ import annotations
3
 
4
  from dataclasses import dataclass
5
 
6
  from homeassistant.components.select import SelectEntity, SelectEntityDescription
7
- from homeassistant.const import ATTR_NAME, STATE_ON, EntityCategory
8
  from homeassistant.core import HomeAssistant, callback
9
  from homeassistant.helpers.entity_platform import AddEntitiesCallback
10
 
11
  from . import PlugwiseConfigEntry
12
- from .const import (
13
- AVAILABLE_SCHEDULES,
14
- DHW_MODE,
15
- DHW_MODES,
16
- GATEWAY_MODE,
17
- GATEWAY_MODES,
18
- LOCATION,
19
- LOGGER,
20
- REGULATION_MODE,
21
- REGULATION_MODES,
22
- SELECT_DHW_MODE,
23
- SELECT_GATEWAY_MODE,
24
- SELECT_REGULATION_MODE,
25
- SELECT_SCHEDULE,
26
- SelectOptionsType,
27
- SelectType,
28
- )
29
  from .coordinator import PlugwiseDataUpdateCoordinator
30
  from .entity import PlugwiseEntity
31
  from .util import plugwise_command
32
 
33
- PARALLEL_UPDATES = 0
34
-
35
 
36
  @dataclass(frozen=True, kw_only=True)
37
  class PlugwiseSelectEntityDescription(SelectEntityDescription):
@@ -43,27 +26,27 @@
43
 
44
  SELECT_TYPES = (
45
  PlugwiseSelectEntityDescription(
46
- key=SELECT_SCHEDULE,
47
- translation_key=SELECT_SCHEDULE,
48
- options_key=AVAILABLE_SCHEDULES,
49
  ),
50
  PlugwiseSelectEntityDescription(
51
- key=SELECT_REGULATION_MODE,
52
- translation_key=REGULATION_MODE,
53
  entity_category=EntityCategory.CONFIG,
54
- options_key=REGULATION_MODES,
55
  ),
56
  PlugwiseSelectEntityDescription(
57
- key=SELECT_DHW_MODE,
58
- translation_key=DHW_MODE,
59
  entity_category=EntityCategory.CONFIG,
60
- options_key=DHW_MODES,
61
  ),
62
  PlugwiseSelectEntityDescription(
63
- key=SELECT_GATEWAY_MODE,
64
- translation_key=GATEWAY_MODE,
65
  entity_category=EntityCategory.CONFIG,
66
- options_key=GATEWAY_MODES,
67
  ),
68
  )
69
 
@@ -73,7 +56,7 @@
73
  entry: PlugwiseConfigEntry,
74
  async_add_entities: AddEntitiesCallback,
75
  ) -> None:
76
- """Set up the Smile selector from a ConfigEntry."""
77
  coordinator = entry.runtime_data
78
 
79
  @callback
@@ -82,22 +65,15 @@
82
  if not coordinator.new_devices:
83
  return
84
 
85
- entities: list[PlugwiseSelectEntity] = []
86
- for device_id, device in coordinator.data.devices.items():
87
- for description in SELECT_TYPES:
88
- if description.options_key in device:
89
- entities.append(
90
- PlugwiseSelectEntity(coordinator, device_id, description)
91
- )
92
- LOGGER.debug(
93
- "Add %s %s selector", device[ATTR_NAME], description.translation_key
94
- )
95
-
96
- async_add_entities(entities)
97
-
98
- entry.async_on_unload(coordinator.async_add_listener(_add_entities))
99
 
100
  _add_entities()
 
101
 
102
 
103
  class PlugwiseSelectEntity(PlugwiseEntity, SelectEntity):
@@ -135,8 +111,3 @@
135
  await self.coordinator.api.set_select(
136
  self.entity_description.key, self.device[LOCATION], option, STATE_ON
137
  )
138
- LOGGER.debug(
139
- "Set %s to %s was successful.",
140
- self.entity_description.key,
141
- option,
142
- )
 
1
  """Plugwise Select component for Home Assistant."""
2
+
3
  from __future__ import annotations
4
 
5
  from dataclasses import dataclass
6
 
7
  from homeassistant.components.select import SelectEntity, SelectEntityDescription
8
+ from homeassistant.const import STATE_ON, EntityCategory
9
  from homeassistant.core import HomeAssistant, callback
10
  from homeassistant.helpers.entity_platform import AddEntitiesCallback
11
 
12
  from . import PlugwiseConfigEntry
13
+ from .const import LOCATION, SelectOptionsType, SelectType
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  from .coordinator import PlugwiseDataUpdateCoordinator
15
  from .entity import PlugwiseEntity
16
  from .util import plugwise_command
17
 
 
 
18
 
19
  @dataclass(frozen=True, kw_only=True)
20
  class PlugwiseSelectEntityDescription(SelectEntityDescription):
 
26
 
27
  SELECT_TYPES = (
28
  PlugwiseSelectEntityDescription(
29
+ key="select_schedule",
30
+ translation_key="select_schedule",
31
+ options_key="available_schedules",
32
  ),
33
  PlugwiseSelectEntityDescription(
34
+ key="select_regulation_mode",
35
+ translation_key="regulation_mode",
36
  entity_category=EntityCategory.CONFIG,
37
+ options_key="regulation_modes",
38
  ),
39
  PlugwiseSelectEntityDescription(
40
+ key="select_dhw_mode",
41
+ translation_key="dhw_mode",
42
  entity_category=EntityCategory.CONFIG,
43
+ options_key="dhw_modes",
44
  ),
45
  PlugwiseSelectEntityDescription(
46
+ key="select_gateway_mode",
47
+ translation_key="gateway_mode",
48
  entity_category=EntityCategory.CONFIG,
49
+ options_key="gateway_modes",
50
  ),
51
  )
52
 
 
56
  entry: PlugwiseConfigEntry,
57
  async_add_entities: AddEntitiesCallback,
58
  ) -> None:
59
+ """Set up the Smile selector from a config entry."""
60
  coordinator = entry.runtime_data
61
 
62
  @callback
 
65
  if not coordinator.new_devices:
66
  return
67
 
68
+ async_add_entities(
69
+ PlugwiseSelectEntity(coordinator, device_id, description)
70
+ for device_id in coordinator.new_devices
71
+ for description in SELECT_TYPES
72
+ if description.options_key in coordinator.data.devices[device_id]
73
+ )
 
 
 
 
 
 
 
 
74
 
75
  _add_entities()
76
+ entry.async_on_unload(coordinator.async_add_listener(_add_entities))
77
 
78
 
79
  class PlugwiseSelectEntity(PlugwiseEntity, SelectEntity):
 
111
  await self.coordinator.api.set_select(
112
  self.entity_description.key, self.device[LOCATION], option, STATE_ON
113
  )
 
 
 
 
 
/home/runner/work/progress/progress/clones/beta/{beta/custom_components → ha-core/homeassistant/components}/plugwise/sensor.py RENAMED
@@ -1,4 +1,5 @@
1
  """Plugwise Sensor component for Home Assistant."""
 
2
  from __future__ import annotations
3
 
4
  from dataclasses import dataclass
@@ -12,8 +13,6 @@
12
  SensorStateClass,
13
  )
14
  from homeassistant.const import (
15
- ATTR_NAME,
16
- ATTR_TEMPERATURE,
17
  LIGHT_LUX,
18
  PERCENTAGE,
19
  EntityCategory,
@@ -29,60 +28,9 @@
29
  from homeassistant.helpers.entity_platform import AddEntitiesCallback
30
 
31
  from . import PlugwiseConfigEntry
32
- from .const import (
33
- DHW_SETPOINT,
34
- DHW_TEMP,
35
- EL_CONS_INTERVAL,
36
- EL_CONS_OP_CUMULATIVE,
37
- EL_CONS_OP_INTERVAL,
38
- EL_CONS_OP_POINT,
39
- EL_CONS_P_CUMULATIVE,
40
- EL_CONS_P_INTERVAL,
41
- EL_CONS_P_POINT,
42
- EL_CONS_POINT,
43
- EL_CONSUMED,
44
- EL_PH1_CONSUMED,
45
- EL_PH1_PRODUCED,
46
- EL_PH2_CONSUMED,
47
- EL_PH2_PRODUCED,
48
- EL_PH3_CONSUMED,
49
- EL_PH3_PRODUCED,
50
- EL_PROD_INTERVAL,
51
- EL_PROD_OP_CUMULATIVE,
52
- EL_PROD_OP_INTERVAL,
53
- EL_PROD_OP_POINT,
54
- EL_PROD_P_CUMULATIVE,
55
- EL_PROD_P_INTERVAL,
56
- EL_PROD_P_POINT,
57
- EL_PROD_POINT,
58
- EL_PRODUCED,
59
- GAS_CONS_CUMULATIVE,
60
- GAS_CONS_INTERVAL,
61
- INTENDED_BOILER_TEMP,
62
- LOGGER,
63
- MOD_LEVEL,
64
- NET_EL_CUMULATIVE,
65
- NET_EL_POINT,
66
- OUTDOOR_AIR_TEMP,
67
- OUTDOOR_TEMP,
68
- RETURN_TEMP,
69
- SENSORS,
70
- TARGET_TEMP,
71
- TARGET_TEMP_HIGH,
72
- TARGET_TEMP_LOW,
73
- TEMP_DIFF,
74
- VALVE_POS,
75
- VOLTAGE_PH1,
76
- VOLTAGE_PH2,
77
- VOLTAGE_PH3,
78
- WATER_PRESSURE,
79
- WATER_TEMP,
80
- )
81
  from .coordinator import PlugwiseDataUpdateCoordinator
82
  from .entity import PlugwiseEntity
83
 
84
- PARALLEL_UPDATES = 0
85
-
86
 
87
  @dataclass(frozen=True)
88
  class PlugwiseSensorEntityDescription(SensorEntityDescription):
@@ -91,17 +39,17 @@
91
  key: SensorType
92
 
93
 
94
- PLUGWISE_SENSORS: tuple[PlugwiseSensorEntityDescription, ...] = (
95
  PlugwiseSensorEntityDescription(
96
- key=TARGET_TEMP,
97
- translation_key=TARGET_TEMP,
98
  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
99
  device_class=SensorDeviceClass.TEMPERATURE,
100
  state_class=SensorStateClass.MEASUREMENT,
101
  entity_category=EntityCategory.DIAGNOSTIC,
102
  ),
103
  PlugwiseSensorEntityDescription(
104
- key=TARGET_TEMP_HIGH,
105
  translation_key="cooling_setpoint",
106
  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
107
  device_class=SensorDeviceClass.TEMPERATURE,
@@ -109,7 +57,7 @@
109
  entity_category=EntityCategory.DIAGNOSTIC,
110
  ),
111
  PlugwiseSensorEntityDescription(
112
- key=TARGET_TEMP_LOW,
113
  translation_key="heating_setpoint",
114
  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
115
  device_class=SensorDeviceClass.TEMPERATURE,
@@ -117,277 +65,276 @@
117
  entity_category=EntityCategory.DIAGNOSTIC,
118
  ),
119
  PlugwiseSensorEntityDescription(
120
- key=ATTR_TEMPERATURE,
121
  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
122
  device_class=SensorDeviceClass.TEMPERATURE,
123
  entity_category=EntityCategory.DIAGNOSTIC,
124
  state_class=SensorStateClass.MEASUREMENT,
125
  ),
126
  PlugwiseSensorEntityDescription(
127
- key=INTENDED_BOILER_TEMP,
128
- translation_key=INTENDED_BOILER_TEMP,
129
  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
130
  device_class=SensorDeviceClass.TEMPERATURE,
131
  entity_category=EntityCategory.DIAGNOSTIC,
132
  state_class=SensorStateClass.MEASUREMENT,
133
  ),
134
  PlugwiseSensorEntityDescription(
135
- key=TEMP_DIFF,
136
- translation_key=TEMP_DIFF,
137
  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
138
  device_class=SensorDeviceClass.TEMPERATURE,
139
  entity_category=EntityCategory.DIAGNOSTIC,
140
  state_class=SensorStateClass.MEASUREMENT,
141
  ),
142
  PlugwiseSensorEntityDescription(
143
- key=OUTDOOR_TEMP,
144
- translation_key=OUTDOOR_TEMP,
145
  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
146
  device_class=SensorDeviceClass.TEMPERATURE,
147
  state_class=SensorStateClass.MEASUREMENT,
148
- suggested_display_precision=1,
149
  ),
150
  PlugwiseSensorEntityDescription(
151
- key=OUTDOOR_AIR_TEMP,
152
- translation_key=OUTDOOR_AIR_TEMP,
153
  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
154
  device_class=SensorDeviceClass.TEMPERATURE,
155
  entity_category=EntityCategory.DIAGNOSTIC,
156
  state_class=SensorStateClass.MEASUREMENT,
157
  ),
158
  PlugwiseSensorEntityDescription(
159
- key=WATER_TEMP,
160
- translation_key=WATER_TEMP,
161
  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
162
  device_class=SensorDeviceClass.TEMPERATURE,
163
  entity_category=EntityCategory.DIAGNOSTIC,
164
  state_class=SensorStateClass.MEASUREMENT,
165
  ),
166
  PlugwiseSensorEntityDescription(
167
- key=RETURN_TEMP,
168
- translation_key=RETURN_TEMP,
169
  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
170
  device_class=SensorDeviceClass.TEMPERATURE,
171
  entity_category=EntityCategory.DIAGNOSTIC,
172
  state_class=SensorStateClass.MEASUREMENT,
173
  ),
174
  PlugwiseSensorEntityDescription(
175
- key=EL_CONSUMED,
176
- translation_key=EL_CONSUMED,
177
  native_unit_of_measurement=UnitOfPower.WATT,
178
  device_class=SensorDeviceClass.POWER,
179
  state_class=SensorStateClass.MEASUREMENT,
180
  ),
181
  PlugwiseSensorEntityDescription(
182
- key=EL_PRODUCED,
183
- translation_key=EL_PRODUCED,
184
  native_unit_of_measurement=UnitOfPower.WATT,
185
  device_class=SensorDeviceClass.POWER,
186
  state_class=SensorStateClass.MEASUREMENT,
187
  entity_registry_enabled_default=False,
188
  ),
189
  PlugwiseSensorEntityDescription(
190
- key=EL_CONS_INTERVAL,
191
- translation_key=EL_CONS_INTERVAL,
192
  native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
193
  device_class=SensorDeviceClass.ENERGY,
194
  state_class=SensorStateClass.TOTAL,
195
  ),
196
  PlugwiseSensorEntityDescription(
197
- key=EL_CONS_P_INTERVAL,
198
- translation_key=EL_CONS_P_INTERVAL,
199
  native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
200
  device_class=SensorDeviceClass.ENERGY,
201
  state_class=SensorStateClass.TOTAL,
202
  ),
203
  PlugwiseSensorEntityDescription(
204
- key=EL_CONS_OP_INTERVAL,
205
- translation_key=EL_CONS_OP_INTERVAL,
206
  native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
207
  device_class=SensorDeviceClass.ENERGY,
208
  state_class=SensorStateClass.TOTAL,
209
  ),
210
  PlugwiseSensorEntityDescription(
211
- key=EL_PROD_INTERVAL,
212
- translation_key=EL_PROD_INTERVAL,
213
  native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
214
  device_class=SensorDeviceClass.ENERGY,
215
  state_class=SensorStateClass.TOTAL,
216
  entity_registry_enabled_default=False,
217
  ),
218
  PlugwiseSensorEntityDescription(
219
- key=EL_PROD_P_INTERVAL,
220
- translation_key=EL_PROD_P_INTERVAL,
221
  native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
222
  device_class=SensorDeviceClass.ENERGY,
223
  state_class=SensorStateClass.TOTAL,
224
  ),
225
  PlugwiseSensorEntityDescription(
226
- key=EL_PROD_OP_INTERVAL,
227
- translation_key=EL_PROD_OP_INTERVAL,
228
  native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
229
  device_class=SensorDeviceClass.ENERGY,
230
  state_class=SensorStateClass.TOTAL,
231
  ),
232
  PlugwiseSensorEntityDescription(
233
- key=EL_CONS_POINT,
234
- translation_key=EL_CONS_POINT,
235
  device_class=SensorDeviceClass.POWER,
236
  native_unit_of_measurement=UnitOfPower.WATT,
237
  state_class=SensorStateClass.MEASUREMENT,
238
  ),
239
  PlugwiseSensorEntityDescription(
240
- key=EL_CONS_OP_POINT,
241
- translation_key=EL_CONS_OP_POINT,
242
  native_unit_of_measurement=UnitOfPower.WATT,
243
  device_class=SensorDeviceClass.POWER,
244
  state_class=SensorStateClass.MEASUREMENT,
245
  ),
246
  PlugwiseSensorEntityDescription(
247
- key=EL_CONS_P_POINT,
248
- translation_key=EL_CONS_P_POINT,
249
  native_unit_of_measurement=UnitOfPower.WATT,
250
  device_class=SensorDeviceClass.POWER,
251
  state_class=SensorStateClass.MEASUREMENT,
252
  ),
253
  PlugwiseSensorEntityDescription(
254
- key=EL_CONS_OP_CUMULATIVE,
255
- translation_key=EL_CONS_OP_CUMULATIVE,
256
  native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
257
  device_class=SensorDeviceClass.ENERGY,
258
  state_class=SensorStateClass.TOTAL_INCREASING,
259
  ),
260
  PlugwiseSensorEntityDescription(
261
- key=EL_CONS_P_CUMULATIVE,
262
- translation_key=EL_CONS_P_CUMULATIVE,
263
  native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
264
  device_class=SensorDeviceClass.ENERGY,
265
  state_class=SensorStateClass.TOTAL_INCREASING,
266
  ),
267
  PlugwiseSensorEntityDescription(
268
- key=EL_PROD_POINT,
269
- translation_key=EL_PROD_POINT,
270
  device_class=SensorDeviceClass.POWER,
271
  native_unit_of_measurement=UnitOfPower.WATT,
272
  state_class=SensorStateClass.MEASUREMENT,
273
  ),
274
  PlugwiseSensorEntityDescription(
275
- key=EL_PROD_OP_POINT,
276
- translation_key=EL_PROD_OP_POINT,
277
  native_unit_of_measurement=UnitOfPower.WATT,
278
  device_class=SensorDeviceClass.POWER,
279
  state_class=SensorStateClass.MEASUREMENT,
280
  ),
281
  PlugwiseSensorEntityDescription(
282
- key=EL_PROD_P_POINT,
283
- translation_key=EL_PROD_P_POINT,
284
  native_unit_of_measurement=UnitOfPower.WATT,
285
  device_class=SensorDeviceClass.POWER,
286
  state_class=SensorStateClass.MEASUREMENT,
287
  ),
288
  PlugwiseSensorEntityDescription(
289
- key=EL_PROD_OP_CUMULATIVE,
290
- translation_key=EL_PROD_OP_CUMULATIVE,
291
  native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
292
  device_class=SensorDeviceClass.ENERGY,
293
  state_class=SensorStateClass.TOTAL_INCREASING,
294
  ),
295
  PlugwiseSensorEntityDescription(
296
- key=EL_PROD_P_CUMULATIVE,
297
- translation_key=EL_PROD_P_CUMULATIVE,
298
  native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
299
  device_class=SensorDeviceClass.ENERGY,
300
  state_class=SensorStateClass.TOTAL_INCREASING,
301
  ),
302
  PlugwiseSensorEntityDescription(
303
- key=EL_PH1_CONSUMED,
304
- translation_key=EL_PH1_CONSUMED,
305
  device_class=SensorDeviceClass.POWER,
306
  native_unit_of_measurement=UnitOfPower.WATT,
307
  state_class=SensorStateClass.MEASUREMENT,
308
  ),
309
  PlugwiseSensorEntityDescription(
310
- key=EL_PH2_CONSUMED,
311
- translation_key=EL_PH2_CONSUMED,
312
  device_class=SensorDeviceClass.POWER,
313
  native_unit_of_measurement=UnitOfPower.WATT,
314
  state_class=SensorStateClass.MEASUREMENT,
315
  ),
316
  PlugwiseSensorEntityDescription(
317
- key=EL_PH3_CONSUMED,
318
- translation_key=EL_PH3_CONSUMED,
319
  device_class=SensorDeviceClass.POWER,
320
  native_unit_of_measurement=UnitOfPower.WATT,
321
  state_class=SensorStateClass.MEASUREMENT,
322
  ),
323
  PlugwiseSensorEntityDescription(
324
- key=EL_PH1_PRODUCED,
325
- translation_key=EL_PH1_PRODUCED,
326
  device_class=SensorDeviceClass.POWER,
327
  native_unit_of_measurement=UnitOfPower.WATT,
328
  state_class=SensorStateClass.MEASUREMENT,
329
  ),
330
  PlugwiseSensorEntityDescription(
331
- key=EL_PH2_PRODUCED,
332
- translation_key=EL_PH2_PRODUCED,
333
  device_class=SensorDeviceClass.POWER,
334
  native_unit_of_measurement=UnitOfPower.WATT,
335
  state_class=SensorStateClass.MEASUREMENT,
336
  ),
337
  PlugwiseSensorEntityDescription(
338
- key=EL_PH3_PRODUCED,
339
- translation_key=EL_PH3_PRODUCED,
340
  device_class=SensorDeviceClass.POWER,
341
  native_unit_of_measurement=UnitOfPower.WATT,
342
  state_class=SensorStateClass.MEASUREMENT,
343
  ),
344
  PlugwiseSensorEntityDescription(
345
- key=VOLTAGE_PH1,
346
- translation_key=VOLTAGE_PH1,
347
  device_class=SensorDeviceClass.VOLTAGE,
348
  native_unit_of_measurement=UnitOfElectricPotential.VOLT,
349
  state_class=SensorStateClass.MEASUREMENT,
350
  entity_registry_enabled_default=False,
351
  ),
352
  PlugwiseSensorEntityDescription(
353
- key=VOLTAGE_PH2,
354
- translation_key=VOLTAGE_PH2,
355
  device_class=SensorDeviceClass.VOLTAGE,
356
  native_unit_of_measurement=UnitOfElectricPotential.VOLT,
357
  state_class=SensorStateClass.MEASUREMENT,
358
  entity_registry_enabled_default=False,
359
  ),
360
  PlugwiseSensorEntityDescription(
361
- key=VOLTAGE_PH3,
362
- translation_key=VOLTAGE_PH3,
363
  device_class=SensorDeviceClass.VOLTAGE,
364
  native_unit_of_measurement=UnitOfElectricPotential.VOLT,
365
  state_class=SensorStateClass.MEASUREMENT,
366
  entity_registry_enabled_default=False,
367
  ),
368
  PlugwiseSensorEntityDescription(
369
- key=GAS_CONS_INTERVAL,
370
- translation_key=GAS_CONS_INTERVAL,
371
  native_unit_of_measurement=UnitOfVolumeFlowRate.CUBIC_METERS_PER_HOUR,
372
  state_class=SensorStateClass.MEASUREMENT,
373
  ),
374
  PlugwiseSensorEntityDescription(
375
- key=GAS_CONS_CUMULATIVE,
376
- translation_key=GAS_CONS_CUMULATIVE,
377
  native_unit_of_measurement=UnitOfVolume.CUBIC_METERS,
378
  device_class=SensorDeviceClass.GAS,
379
  state_class=SensorStateClass.TOTAL,
380
  ),
381
  PlugwiseSensorEntityDescription(
382
- key=NET_EL_POINT,
383
- translation_key=NET_EL_POINT,
384
  native_unit_of_measurement=UnitOfPower.WATT,
385
  device_class=SensorDeviceClass.POWER,
386
  state_class=SensorStateClass.MEASUREMENT,
387
  ),
388
  PlugwiseSensorEntityDescription(
389
- key=NET_EL_CUMULATIVE,
390
- translation_key=NET_EL_CUMULATIVE,
391
  native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
392
  device_class=SensorDeviceClass.ENERGY,
393
  state_class=SensorStateClass.TOTAL,
@@ -407,22 +354,22 @@
407
  entity_category=EntityCategory.DIAGNOSTIC,
408
  ),
409
  PlugwiseSensorEntityDescription(
410
- key=MOD_LEVEL,
411
- translation_key=MOD_LEVEL,
412
  native_unit_of_measurement=PERCENTAGE,
413
  entity_category=EntityCategory.DIAGNOSTIC,
414
  state_class=SensorStateClass.MEASUREMENT,
415
  ),
416
  PlugwiseSensorEntityDescription(
417
- key=VALVE_POS,
418
- translation_key=VALVE_POS,
419
  entity_category=EntityCategory.DIAGNOSTIC,
420
  native_unit_of_measurement=PERCENTAGE,
421
  state_class=SensorStateClass.MEASUREMENT,
422
  ),
423
  PlugwiseSensorEntityDescription(
424
- key=WATER_PRESSURE,
425
- translation_key=WATER_PRESSURE,
426
  native_unit_of_measurement=UnitOfPressure.BAR,
427
  device_class=SensorDeviceClass.PRESSURE,
428
  entity_category=EntityCategory.DIAGNOSTIC,
@@ -435,16 +382,16 @@
435
  state_class=SensorStateClass.MEASUREMENT,
436
  ),
437
  PlugwiseSensorEntityDescription(
438
- key=DHW_TEMP,
439
- translation_key=DHW_TEMP,
440
  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
441
  device_class=SensorDeviceClass.TEMPERATURE,
442
  entity_category=EntityCategory.DIAGNOSTIC,
443
  state_class=SensorStateClass.MEASUREMENT,
444
  ),
445
  PlugwiseSensorEntityDescription(
446
- key=DHW_SETPOINT,
447
- translation_key=DHW_SETPOINT,
448
  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
449
  device_class=SensorDeviceClass.TEMPERATURE,
450
  entity_category=EntityCategory.DIAGNOSTIC,
@@ -458,7 +405,7 @@
458
  entry: PlugwiseConfigEntry,
459
  async_add_entities: AddEntitiesCallback,
460
  ) -> None:
461
- """Set up the Smile sensors from a ConfigEntry."""
462
  coordinator = entry.runtime_data
463
 
464
  @callback
@@ -467,29 +414,16 @@
467
  if not coordinator.new_devices:
468
  return
469
 
470
- entities: list[PlugwiseSensorEntity] = []
471
- for device_id, device in coordinator.data.devices.items():
472
- if not (sensors := device.get(SENSORS)):
473
- continue
474
- for description in PLUGWISE_SENSORS:
475
- if description.key not in sensors:
476
- continue
477
- entities.append(
478
- PlugwiseSensorEntity(
479
- coordinator,
480
- device_id,
481
- description,
482
- )
483
- )
484
- LOGGER.debug(
485
- "Add %s %s sensor", device[ATTR_NAME], description.translation_key or description.key
486
- )
487
-
488
- async_add_entities(entities)
489
-
490
- entry.async_on_unload(coordinator.async_add_listener(_add_entities))
491
 
492
  _add_entities()
 
493
 
494
 
495
  class PlugwiseSensorEntity(PlugwiseEntity, SensorEntity):
@@ -511,4 +445,4 @@
511
  @property
512
  def native_value(self) -> int | float:
513
  """Return the value reported by the sensor."""
514
- return self.device[SENSORS][self.entity_description.key]
 
1
  """Plugwise Sensor component for Home Assistant."""
2
+
3
  from __future__ import annotations
4
 
5
  from dataclasses import dataclass
 
13
  SensorStateClass,
14
  )
15
  from homeassistant.const import (
 
 
16
  LIGHT_LUX,
17
  PERCENTAGE,
18
  EntityCategory,
 
28
  from homeassistant.helpers.entity_platform import AddEntitiesCallback
29
 
30
  from . import PlugwiseConfigEntry
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
  from .coordinator import PlugwiseDataUpdateCoordinator
32
  from .entity import PlugwiseEntity
33
 
 
 
34
 
35
  @dataclass(frozen=True)
36
  class PlugwiseSensorEntityDescription(SensorEntityDescription):
 
39
  key: SensorType
40
 
41
 
42
+ SENSORS: tuple[PlugwiseSensorEntityDescription, ...] = (
43
  PlugwiseSensorEntityDescription(
44
+ key="setpoint",
45
+ translation_key="setpoint",
46
  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
47
  device_class=SensorDeviceClass.TEMPERATURE,
48
  state_class=SensorStateClass.MEASUREMENT,
49
  entity_category=EntityCategory.DIAGNOSTIC,
50
  ),
51
  PlugwiseSensorEntityDescription(
52
+ key="setpoint_high",
53
  translation_key="cooling_setpoint",
54
  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
55
  device_class=SensorDeviceClass.TEMPERATURE,
 
57
  entity_category=EntityCategory.DIAGNOSTIC,
58
  ),
59
  PlugwiseSensorEntityDescription(
60
+ key="setpoint_low",
61
  translation_key="heating_setpoint",
62
  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
63
  device_class=SensorDeviceClass.TEMPERATURE,
 
65
  entity_category=EntityCategory.DIAGNOSTIC,
66
  ),
67
  PlugwiseSensorEntityDescription(
68
+ key="temperature",
69
  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
70
  device_class=SensorDeviceClass.TEMPERATURE,
71
  entity_category=EntityCategory.DIAGNOSTIC,
72
  state_class=SensorStateClass.MEASUREMENT,
73
  ),
74
  PlugwiseSensorEntityDescription(
75
+ key="intended_boiler_temperature",
76
+ translation_key="intended_boiler_temperature",
77
  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
78
  device_class=SensorDeviceClass.TEMPERATURE,
79
  entity_category=EntityCategory.DIAGNOSTIC,
80
  state_class=SensorStateClass.MEASUREMENT,
81
  ),
82
  PlugwiseSensorEntityDescription(
83
+ key="temperature_difference",
84
+ translation_key="temperature_difference",
85
  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
86
  device_class=SensorDeviceClass.TEMPERATURE,
87
  entity_category=EntityCategory.DIAGNOSTIC,
88
  state_class=SensorStateClass.MEASUREMENT,
89
  ),
90
  PlugwiseSensorEntityDescription(
91
+ key="outdoor_temperature",
92
+ translation_key="outdoor_temperature",
93
  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
94
  device_class=SensorDeviceClass.TEMPERATURE,
95
  state_class=SensorStateClass.MEASUREMENT,
 
96
  ),
97
  PlugwiseSensorEntityDescription(
98
+ key="outdoor_air_temperature",
99
+ translation_key="outdoor_air_temperature",
100
  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
101
  device_class=SensorDeviceClass.TEMPERATURE,
102
  entity_category=EntityCategory.DIAGNOSTIC,
103
  state_class=SensorStateClass.MEASUREMENT,
104
  ),
105
  PlugwiseSensorEntityDescription(
106
+ key="water_temperature",
107
+ translation_key="water_temperature",
108
  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
109
  device_class=SensorDeviceClass.TEMPERATURE,
110
  entity_category=EntityCategory.DIAGNOSTIC,
111
  state_class=SensorStateClass.MEASUREMENT,
112
  ),
113
  PlugwiseSensorEntityDescription(
114
+ key="return_temperature",
115
+ translation_key="return_temperature",
116
  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
117
  device_class=SensorDeviceClass.TEMPERATURE,
118
  entity_category=EntityCategory.DIAGNOSTIC,
119
  state_class=SensorStateClass.MEASUREMENT,
120
  ),
121
  PlugwiseSensorEntityDescription(
122
+ key="electricity_consumed",
123
+ translation_key="electricity_consumed",
124
  native_unit_of_measurement=UnitOfPower.WATT,
125
  device_class=SensorDeviceClass.POWER,
126
  state_class=SensorStateClass.MEASUREMENT,
127
  ),
128
  PlugwiseSensorEntityDescription(
129
+ key="electricity_produced",
130
+ translation_key="electricity_produced",
131
  native_unit_of_measurement=UnitOfPower.WATT,
132
  device_class=SensorDeviceClass.POWER,
133
  state_class=SensorStateClass.MEASUREMENT,
134
  entity_registry_enabled_default=False,
135
  ),
136
  PlugwiseSensorEntityDescription(
137
+ key="electricity_consumed_interval",
138
+ translation_key="electricity_consumed_interval",
139
  native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
140
  device_class=SensorDeviceClass.ENERGY,
141
  state_class=SensorStateClass.TOTAL,
142
  ),
143
  PlugwiseSensorEntityDescription(
144
+ key="electricity_consumed_peak_interval",
145
+ translation_key="electricity_consumed_peak_interval",
146
  native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
147
  device_class=SensorDeviceClass.ENERGY,
148
  state_class=SensorStateClass.TOTAL,
149
  ),
150
  PlugwiseSensorEntityDescription(
151
+ key="electricity_consumed_off_peak_interval",
152
+ translation_key="electricity_consumed_off_peak_interval",
153
  native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
154
  device_class=SensorDeviceClass.ENERGY,
155
  state_class=SensorStateClass.TOTAL,
156
  ),
157
  PlugwiseSensorEntityDescription(
158
+ key="electricity_produced_interval",
159
+ translation_key="electricity_produced_interval",
160
  native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
161
  device_class=SensorDeviceClass.ENERGY,
162
  state_class=SensorStateClass.TOTAL,
163
  entity_registry_enabled_default=False,
164
  ),
165
  PlugwiseSensorEntityDescription(
166
+ key="electricity_produced_peak_interval",
167
+ translation_key="electricity_produced_peak_interval",
168
  native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
169
  device_class=SensorDeviceClass.ENERGY,
170
  state_class=SensorStateClass.TOTAL,
171
  ),
172
  PlugwiseSensorEntityDescription(
173
+ key="electricity_produced_off_peak_interval",
174
+ translation_key="electricity_produced_off_peak_interval",
175
  native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
176
  device_class=SensorDeviceClass.ENERGY,
177
  state_class=SensorStateClass.TOTAL,
178
  ),
179
  PlugwiseSensorEntityDescription(
180
+ key="electricity_consumed_point",
181
+ translation_key="electricity_consumed_point",
182
  device_class=SensorDeviceClass.POWER,
183
  native_unit_of_measurement=UnitOfPower.WATT,
184
  state_class=SensorStateClass.MEASUREMENT,
185
  ),
186
  PlugwiseSensorEntityDescription(
187
+ key="electricity_consumed_off_peak_point",
188
+ translation_key="electricity_consumed_off_peak_point",
189
  native_unit_of_measurement=UnitOfPower.WATT,
190
  device_class=SensorDeviceClass.POWER,
191
  state_class=SensorStateClass.MEASUREMENT,
192
  ),
193
  PlugwiseSensorEntityDescription(
194
+ key="electricity_consumed_peak_point",
195
+ translation_key="electricity_consumed_peak_point",
196
  native_unit_of_measurement=UnitOfPower.WATT,
197
  device_class=SensorDeviceClass.POWER,
198
  state_class=SensorStateClass.MEASUREMENT,
199
  ),
200
  PlugwiseSensorEntityDescription(
201
+ key="electricity_consumed_off_peak_cumulative",
202
+ translation_key="electricity_consumed_off_peak_cumulative",
203
  native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
204
  device_class=SensorDeviceClass.ENERGY,
205
  state_class=SensorStateClass.TOTAL_INCREASING,
206
  ),
207
  PlugwiseSensorEntityDescription(
208
+ key="electricity_consumed_peak_cumulative",
209
+ translation_key="electricity_consumed_peak_cumulative",
210
  native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
211
  device_class=SensorDeviceClass.ENERGY,
212
  state_class=SensorStateClass.TOTAL_INCREASING,
213
  ),
214
  PlugwiseSensorEntityDescription(
215
+ key="electricity_produced_point",
216
+ translation_key="electricity_produced_point",
217
  device_class=SensorDeviceClass.POWER,
218
  native_unit_of_measurement=UnitOfPower.WATT,
219
  state_class=SensorStateClass.MEASUREMENT,
220
  ),
221
  PlugwiseSensorEntityDescription(
222
+ key="electricity_produced_off_peak_point",
223
+ translation_key="electricity_produced_off_peak_point",
224
  native_unit_of_measurement=UnitOfPower.WATT,
225
  device_class=SensorDeviceClass.POWER,
226
  state_class=SensorStateClass.MEASUREMENT,
227
  ),
228
  PlugwiseSensorEntityDescription(
229
+ key="electricity_produced_peak_point",
230
+ translation_key="electricity_produced_peak_point",
231
  native_unit_of_measurement=UnitOfPower.WATT,
232
  device_class=SensorDeviceClass.POWER,
233
  state_class=SensorStateClass.MEASUREMENT,
234
  ),
235
  PlugwiseSensorEntityDescription(
236
+ key="electricity_produced_off_peak_cumulative",
237
+ translation_key="electricity_produced_off_peak_cumulative",
238
  native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
239
  device_class=SensorDeviceClass.ENERGY,
240
  state_class=SensorStateClass.TOTAL_INCREASING,
241
  ),
242
  PlugwiseSensorEntityDescription(
243
+ key="electricity_produced_peak_cumulative",
244
+ translation_key="electricity_produced_peak_cumulative",
245
  native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
246
  device_class=SensorDeviceClass.ENERGY,
247
  state_class=SensorStateClass.TOTAL_INCREASING,
248
  ),
249
  PlugwiseSensorEntityDescription(
250
+ key="electricity_phase_one_consumed",
251
+ translation_key="electricity_phase_one_consumed",
252
  device_class=SensorDeviceClass.POWER,
253
  native_unit_of_measurement=UnitOfPower.WATT,
254
  state_class=SensorStateClass.MEASUREMENT,
255
  ),
256
  PlugwiseSensorEntityDescription(
257
+ key="electricity_phase_two_consumed",
258
+ translation_key="electricity_phase_two_consumed",
259
  device_class=SensorDeviceClass.POWER,
260
  native_unit_of_measurement=UnitOfPower.WATT,
261
  state_class=SensorStateClass.MEASUREMENT,
262
  ),
263
  PlugwiseSensorEntityDescription(
264
+ key="electricity_phase_three_consumed",
265
+ translation_key="electricity_phase_three_consumed",
266
  device_class=SensorDeviceClass.POWER,
267
  native_unit_of_measurement=UnitOfPower.WATT,
268
  state_class=SensorStateClass.MEASUREMENT,
269
  ),
270
  PlugwiseSensorEntityDescription(
271
+ key="electricity_phase_one_produced",
272
+ translation_key="electricity_phase_one_produced",
273
  device_class=SensorDeviceClass.POWER,
274
  native_unit_of_measurement=UnitOfPower.WATT,
275
  state_class=SensorStateClass.MEASUREMENT,
276
  ),
277
  PlugwiseSensorEntityDescription(
278
+ key="electricity_phase_two_produced",
279
+ translation_key="electricity_phase_two_produced",
280
  device_class=SensorDeviceClass.POWER,
281
  native_unit_of_measurement=UnitOfPower.WATT,
282
  state_class=SensorStateClass.MEASUREMENT,
283
  ),
284
  PlugwiseSensorEntityDescription(
285
+ key="electricity_phase_three_produced",
286
+ translation_key="electricity_phase_three_produced",
287
  device_class=SensorDeviceClass.POWER,
288
  native_unit_of_measurement=UnitOfPower.WATT,
289
  state_class=SensorStateClass.MEASUREMENT,
290
  ),
291
  PlugwiseSensorEntityDescription(
292
+ key="voltage_phase_one",
293
+ translation_key="voltage_phase_one",
294
  device_class=SensorDeviceClass.VOLTAGE,
295
  native_unit_of_measurement=UnitOfElectricPotential.VOLT,
296
  state_class=SensorStateClass.MEASUREMENT,
297
  entity_registry_enabled_default=False,
298
  ),
299
  PlugwiseSensorEntityDescription(
300
+ key="voltage_phase_two",
301
+ translation_key="voltage_phase_two",
302
  device_class=SensorDeviceClass.VOLTAGE,
303
  native_unit_of_measurement=UnitOfElectricPotential.VOLT,
304
  state_class=SensorStateClass.MEASUREMENT,
305
  entity_registry_enabled_default=False,
306
  ),
307
  PlugwiseSensorEntityDescription(
308
+ key="voltage_phase_three",
309
+ translation_key="voltage_phase_three",
310
  device_class=SensorDeviceClass.VOLTAGE,
311
  native_unit_of_measurement=UnitOfElectricPotential.VOLT,
312
  state_class=SensorStateClass.MEASUREMENT,
313
  entity_registry_enabled_default=False,
314
  ),
315
  PlugwiseSensorEntityDescription(
316
+ key="gas_consumed_interval",
317
+ translation_key="gas_consumed_interval",
318
  native_unit_of_measurement=UnitOfVolumeFlowRate.CUBIC_METERS_PER_HOUR,
319
  state_class=SensorStateClass.MEASUREMENT,
320
  ),
321
  PlugwiseSensorEntityDescription(
322
+ key="gas_consumed_cumulative",
323
+ translation_key="gas_consumed_cumulative",
324
  native_unit_of_measurement=UnitOfVolume.CUBIC_METERS,
325
  device_class=SensorDeviceClass.GAS,
326
  state_class=SensorStateClass.TOTAL,
327
  ),
328
  PlugwiseSensorEntityDescription(
329
+ key="net_electricity_point",
330
+ translation_key="net_electricity_point",
331
  native_unit_of_measurement=UnitOfPower.WATT,
332
  device_class=SensorDeviceClass.POWER,
333
  state_class=SensorStateClass.MEASUREMENT,
334
  ),
335
  PlugwiseSensorEntityDescription(
336
+ key="net_electricity_cumulative",
337
+ translation_key="net_electricity_cumulative",
338
  native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
339
  device_class=SensorDeviceClass.ENERGY,
340
  state_class=SensorStateClass.TOTAL,
 
354
  entity_category=EntityCategory.DIAGNOSTIC,
355
  ),
356
  PlugwiseSensorEntityDescription(
357
+ key="modulation_level",
358
+ translation_key="modulation_level",
359
  native_unit_of_measurement=PERCENTAGE,
360
  entity_category=EntityCategory.DIAGNOSTIC,
361
  state_class=SensorStateClass.MEASUREMENT,
362
  ),
363
  PlugwiseSensorEntityDescription(
364
+ key="valve_position",
365
+ translation_key="valve_position",
366
  entity_category=EntityCategory.DIAGNOSTIC,
367
  native_unit_of_measurement=PERCENTAGE,
368
  state_class=SensorStateClass.MEASUREMENT,
369
  ),
370
  PlugwiseSensorEntityDescription(
371
+ key="water_pressure",
372
+ translation_key="water_pressure",
373
  native_unit_of_measurement=UnitOfPressure.BAR,
374
  device_class=SensorDeviceClass.PRESSURE,
375
  entity_category=EntityCategory.DIAGNOSTIC,
 
382
  state_class=SensorStateClass.MEASUREMENT,
383
  ),
384
  PlugwiseSensorEntityDescription(
385
+ key="dhw_temperature",
386
+ translation_key="dhw_temperature",
387
  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
388
  device_class=SensorDeviceClass.TEMPERATURE,
389
  entity_category=EntityCategory.DIAGNOSTIC,
390
  state_class=SensorStateClass.MEASUREMENT,
391
  ),
392
  PlugwiseSensorEntityDescription(
393
+ key="domestic_hot_water_setpoint",
394
+ translation_key="domestic_hot_water_setpoint",
395
  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
396
  device_class=SensorDeviceClass.TEMPERATURE,
397
  entity_category=EntityCategory.DIAGNOSTIC,
 
405
  entry: PlugwiseConfigEntry,
406
  async_add_entities: AddEntitiesCallback,
407
  ) -> None:
408
+ """Set up the Smile sensors from a config entry."""
409
  coordinator = entry.runtime_data
410
 
411
  @callback
 
414
  if not coordinator.new_devices:
415
  return
416
 
417
+ async_add_entities(
418
+ PlugwiseSensorEntity(coordinator, device_id, description)
419
+ for device_id in coordinator.new_devices
420
+ if (sensors := coordinator.data.devices[device_id].get("sensors"))
421
+ for description in SENSORS
422
+ if description.key in sensors
423
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
424
 
425
  _add_entities()
426
+ entry.async_on_unload(coordinator.async_add_listener(_add_entities))
427
 
428
 
429
  class PlugwiseSensorEntity(PlugwiseEntity, SensorEntity):
 
445
  @property
446
  def native_value(self) -> int | float:
447
  """Return the value reported by the sensor."""
448
+ return self.device["sensors"][self.entity_description.key]
/home/runner/work/progress/progress/clones/beta/{beta/custom_components → ha-core/homeassistant/components}/plugwise/strings.json RENAMED
@@ -1,52 +1,38 @@
1
  {
2
- "options": {
3
- "step": {
4
- "none": {
5
- "title": "No Options available",
6
- "description": "This Integration does not provide any Options"
7
- },
8
- "init": {
9
- "description": "Adjust Smile/Stretch Options",
10
- "data": {
11
- "cooling_on": "Anna: cooling-mode is on",
12
- "scan_interval": "Scan Interval (seconds) *) beta-only option",
13
- "homekit_emulation": "Homekit emulation (i.e. on hvac_off => Away) *) beta-only option",
14
- "refresh_interval": "Frontend refresh-time (1.5 - 5 seconds) *) beta-only option"
15
- }
16
- }
17
- }
18
- },
19
  "config": {
20
  "step": {
21
  "user": {
22
- "title": "Connect to the Plugwise Adam/Smile/Stretch",
23
- "description": "Please enter:",
24
  "data": {
25
- "password": "ID",
26
- "username": "Username",
27
- "host": "IP-address",
28
- "port": "Port number"
 
 
 
29
  }
30
  }
31
  },
32
  "error": {
33
- "cannot_connect": "Failed to connect",
34
- "invalid_auth": "Authentication failed",
35
  "invalid_setup": "Add your Adam instead of your Anna, see the documentation",
36
- "network_down": "Plugwise Zigbee network is down",
37
- "network_timeout": "Network communication timeout",
38
  "response_error": "Invalid XML data, or error indication received",
39
- "stick_init": "Initialization of Plugwise USB-stick failed",
40
- "unknown": "Unknown error!",
41
  "unsupported": "Device with unsupported firmware"
42
  },
43
  "abort": {
44
- "already_configured": "This device is already configured",
45
  "anna_with_adam": "Both Anna and Adam detected. Add your Adam instead of your Anna"
46
  }
47
  },
48
  "entity": {
49
  "binary_sensor": {
 
 
 
50
  "compressor_state": {
51
  "name": "Compressor state"
52
  },
@@ -60,10 +46,10 @@
60
  "name": "Flame state"
61
  },
62
  "heating_state": {
63
- "name": "Heating"
64
  },
65
  "cooling_state": {
66
- "name": "Cooling"
67
  },
68
  "secondary_boiler_state": {
69
  "name": "Secondary boiler state"
@@ -80,14 +66,20 @@
80
  "climate": {
81
  "plugwise": {
82
  "state_attributes": {
 
 
 
83
  "preset_mode": {
84
  "state": {
85
  "asleep": "Night",
86
- "away": "Away",
87
- "home": "Home",
88
  "no_frost": "Anti-frost",
89
  "vacation": "Vacation"
90
  }
 
 
 
91
  }
92
  }
93
  }
@@ -107,20 +99,10 @@
107
  "dhw_mode": {
108
  "name": "DHW mode",
109
  "state": {
 
110
  "auto": "Auto",
111
- "boost": "Boost",
112
- "comfort": "Comfort",
113
- "off": "Off"
114
- }
115
- },
116
- "regulation_mode": {
117
- "name": "Regulation mode",
118
- "state": {
119
- "bleeding_cold": "Bleeding cold",
120
- "bleeding_hot": "Bleeding hot",
121
- "cooling": "Cooling",
122
- "heating": "Heating",
123
- "off": "Off"
124
  }
125
  },
126
  "gateway_mode": {
@@ -131,6 +113,16 @@
131
  "vacation": "Vacation"
132
  }
133
  },
 
 
 
 
 
 
 
 
 
 
134
  "select_schedule": {
135
  "name": "Thermostat schedule",
136
  "state": {
@@ -146,7 +138,7 @@
146
  "name": "Cooling setpoint"
147
  },
148
  "heating_setpoint": {
149
- "name": " Heating setpoint"
150
  },
151
  "intended_boiler_temperature": {
152
  "name": "Intended boiler temperature"
@@ -172,12 +164,6 @@
172
  "electricity_produced": {
173
  "name": "Electricity produced"
174
  },
175
- "electricity_consumed_point": {
176
- "name": "Electricity consumed point"
177
- },
178
- "electricity_produced_point": {
179
- "name": "Electricity produced point"
180
- },
181
  "electricity_consumed_interval": {
182
  "name": "Electricity consumed interval"
183
  },
@@ -196,6 +182,9 @@
196
  "electricity_produced_off_peak_interval": {
197
  "name": "Electricity produced off peak interval"
198
  },
 
 
 
199
  "electricity_consumed_off_peak_point": {
200
  "name": "Electricity consumed off peak point"
201
  },
@@ -208,6 +197,9 @@
208
  "electricity_consumed_peak_cumulative": {
209
  "name": "Electricity consumed peak cumulative"
210
  },
 
 
 
211
  "electricity_produced_off_peak_point": {
212
  "name": "Electricity produced off peak point"
213
  },
@@ -275,28 +267,22 @@
275
  "name": "DHW setpoint"
276
  },
277
  "maximum_boiler_temperature": {
278
- "name": "Maximum boiler temperature setpoint"
279
  }
280
  },
281
  "switch": {
282
  "cooling_ena_switch": {
283
- "name": "Cooling"
284
  },
285
  "dhw_cm_switch": {
286
  "name": "DHW comfort mode"
287
  },
288
  "lock": {
289
- "name": "Lock"
290
  },
291
  "relay": {
292
  "name": "Relay"
293
  }
294
  }
295
- },
296
- "services": {
297
- "delete_notification": {
298
- "name": "Delete Plugwise notification",
299
- "description": "Deletes a Plugwise Notification"
300
- }
301
  }
302
  }
 
1
  {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  "config": {
3
  "step": {
4
  "user": {
5
+ "title": "Connect to the Smile",
6
+ "description": "Please enter",
7
  "data": {
8
+ "password": "Smile ID",
9
+ "host": "[%key:common::config_flow::data::ip%]",
10
+ "port": "[%key:common::config_flow::data::port%]",
11
+ "username": "Smile Username"
12
+ },
13
+ "data_description": {
14
+ "host": "Leave empty if using Auto Discovery"
15
  }
16
  }
17
  },
18
  "error": {
19
+ "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
20
+ "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
21
  "invalid_setup": "Add your Adam instead of your Anna, see the documentation",
 
 
22
  "response_error": "Invalid XML data, or error indication received",
23
+ "unknown": "[%key:common::config_flow::error::unknown%]",
 
24
  "unsupported": "Device with unsupported firmware"
25
  },
26
  "abort": {
27
+ "already_configured": "[%key:common::config_flow::abort::already_configured_service%]",
28
  "anna_with_adam": "Both Anna and Adam detected. Add your Adam instead of your Anna"
29
  }
30
  },
31
  "entity": {
32
  "binary_sensor": {
33
+ "low_battery": {
34
+ "name": "Battery state"
35
+ },
36
  "compressor_state": {
37
  "name": "Compressor state"
38
  },
 
46
  "name": "Flame state"
47
  },
48
  "heating_state": {
49
+ "name": "[%key:component::climate::entity_component::_::state_attributes::hvac_action::state::heating%]"
50
  },
51
  "cooling_state": {
52
+ "name": "[%key:component::climate::entity_component::_::state_attributes::hvac_action::state::cooling%]"
53
  },
54
  "secondary_boiler_state": {
55
  "name": "Secondary boiler state"
 
66
  "climate": {
67
  "plugwise": {
68
  "state_attributes": {
69
+ "available_schemas": {
70
+ "name": "Available schemas"
71
+ },
72
  "preset_mode": {
73
  "state": {
74
  "asleep": "Night",
75
+ "away": "[%key:component::climate::entity_component::_::state_attributes::preset_mode::state::away%]",
76
+ "home": "[%key:common::state::home%]",
77
  "no_frost": "Anti-frost",
78
  "vacation": "Vacation"
79
  }
80
+ },
81
+ "selected_schema": {
82
+ "name": "Selected schema"
83
  }
84
  }
85
  }
 
99
  "dhw_mode": {
100
  "name": "DHW mode",
101
  "state": {
102
+ "off": "[%key:common::state::off%]",
103
  "auto": "Auto",
104
+ "boost": "[%key:component::climate::entity_component::_::state_attributes::preset_mode::state::boost%]",
105
+ "comfort": "[%key:component::climate::entity_component::_::state_attributes::preset_mode::state::comfort%]"
 
 
 
 
 
 
 
 
 
 
 
106
  }
107
  },
108
  "gateway_mode": {
 
113
  "vacation": "Vacation"
114
  }
115
  },
116
+ "regulation_mode": {
117
+ "name": "Regulation mode",
118
+ "state": {
119
+ "bleeding_cold": "Bleeding cold",
120
+ "bleeding_hot": "Bleeding hot",
121
+ "cooling": "[%key:component::climate::entity_component::_::state_attributes::hvac_action::state::cooling%]",
122
+ "heating": "[%key:component::climate::entity_component::_::state_attributes::hvac_action::state::heating%]",
123
+ "off": "[%key:common::state::off%]"
124
+ }
125
+ },
126
  "select_schedule": {
127
  "name": "Thermostat schedule",
128
  "state": {
 
138
  "name": "Cooling setpoint"
139
  },
140
  "heating_setpoint": {
141
+ "name": "Heating setpoint"
142
  },
143
  "intended_boiler_temperature": {
144
  "name": "Intended boiler temperature"
 
164
  "electricity_produced": {
165
  "name": "Electricity produced"
166
  },
 
 
 
 
 
 
167
  "electricity_consumed_interval": {
168
  "name": "Electricity consumed interval"
169
  },
 
182
  "electricity_produced_off_peak_interval": {
183
  "name": "Electricity produced off peak interval"
184
  },
185
+ "electricity_consumed_point": {
186
+ "name": "Electricity consumed point"
187
+ },
188
  "electricity_consumed_off_peak_point": {
189
  "name": "Electricity consumed off peak point"
190
  },
 
197
  "electricity_consumed_peak_cumulative": {
198
  "name": "Electricity consumed peak cumulative"
199
  },
200
+ "electricity_produced_point": {
201
+ "name": "Electricity produced point"
202
+ },
203
  "electricity_produced_off_peak_point": {
204
  "name": "Electricity produced off peak point"
205
  },
 
267
  "name": "DHW setpoint"
268
  },
269
  "maximum_boiler_temperature": {
270
+ "name": "Maximum boiler temperature"
271
  }
272
  },
273
  "switch": {
274
  "cooling_ena_switch": {
275
+ "name": "[%key:component::climate::entity_component::_::state_attributes::hvac_action::state::cooling%]"
276
  },
277
  "dhw_cm_switch": {
278
  "name": "DHW comfort mode"
279
  },
280
  "lock": {
281
+ "name": "[%key:component::lock::title%]"
282
  },
283
  "relay": {
284
  "name": "Relay"
285
  }
286
  }
 
 
 
 
 
 
287
  }
288
  }
/home/runner/work/progress/progress/clones/beta/{beta/custom_components → ha-core/homeassistant/components}/plugwise/switch.py RENAMED
@@ -1,4 +1,5 @@
1
  """Plugwise Switch component for HomeAssistant."""
 
2
  from __future__ import annotations
3
 
4
  from dataclasses import dataclass
@@ -11,20 +12,11 @@
11
  SwitchEntity,
12
  SwitchEntityDescription,
13
  )
14
- from homeassistant.const import ATTR_NAME, EntityCategory
15
  from homeassistant.core import HomeAssistant, callback
16
  from homeassistant.helpers.entity_platform import AddEntitiesCallback
17
 
18
  from . import PlugwiseConfigEntry
19
- from .const import (
20
- COOLING_ENA_SWITCH,
21
- DHW_CM_SWITCH,
22
- LOCK,
23
- LOGGER,
24
- MEMBERS,
25
- RELAY,
26
- SWITCHES,
27
- )
28
  from .coordinator import PlugwiseDataUpdateCoordinator
29
  from .entity import PlugwiseEntity
30
  from .util import plugwise_command
@@ -37,28 +29,26 @@
37
  key: SwitchType
38
 
39
 
40
- PLUGWISE_SWITCHES: tuple[PlugwiseSwitchEntityDescription, ...] = (
41
  PlugwiseSwitchEntityDescription(
42
- key=DHW_CM_SWITCH,
43
- translation_key=DHW_CM_SWITCH,
44
- device_class=SwitchDeviceClass.SWITCH,
45
  entity_category=EntityCategory.CONFIG,
46
  ),
47
  PlugwiseSwitchEntityDescription(
48
- key=LOCK,
49
- translation_key=LOCK,
50
- device_class=SwitchDeviceClass.SWITCH,
51
  entity_category=EntityCategory.CONFIG,
52
  ),
53
  PlugwiseSwitchEntityDescription(
54
- key=RELAY,
55
- translation_key=RELAY,
56
  device_class=SwitchDeviceClass.SWITCH,
57
  ),
58
  PlugwiseSwitchEntityDescription(
59
- key=COOLING_ENA_SWITCH,
60
- translation_key=COOLING_ENA_SWITCH,
61
- device_class=SwitchDeviceClass.SWITCH,
62
  entity_category=EntityCategory.CONFIG,
63
  ),
64
  )
@@ -69,7 +59,7 @@
69
  entry: PlugwiseConfigEntry,
70
  async_add_entities: AddEntitiesCallback,
71
  ) -> None:
72
- """Set up the Smile switches from a ConfigEntry."""
73
  coordinator = entry.runtime_data
74
 
75
  @callback
@@ -78,23 +68,16 @@
78
  if not coordinator.new_devices:
79
  return
80
 
81
- entities: list[PlugwiseSwitchEntity] = []
82
- for device_id, device in coordinator.data.devices.items():
83
- if not (switches := device.get(SWITCHES)):
84
- continue
85
- for description in PLUGWISE_SWITCHES:
86
- if description.key not in switches:
87
- continue
88
- entities.append(PlugwiseSwitchEntity(coordinator, device_id, description))
89
- LOGGER.debug(
90
- "Add %s %s switch", device[ATTR_NAME], description.translation_key
91
- )
92
-
93
- async_add_entities(entities)
94
-
95
- entry.async_on_unload(coordinator.async_add_listener(_add_entities))
96
 
97
  _add_entities()
 
98
 
99
 
100
  class PlugwiseSwitchEntity(PlugwiseEntity, SwitchEntity):
@@ -116,14 +99,14 @@
116
  @property
117
  def is_on(self) -> bool:
118
  """Return True if entity is on."""
119
- return self.device[SWITCHES][self.entity_description.key]
120
 
121
  @plugwise_command
122
  async def async_turn_on(self, **kwargs: Any) -> None:
123
  """Turn the device on."""
124
  await self.coordinator.api.set_switch_state(
125
  self._dev_id,
126
- self.device.get(MEMBERS),
127
  self.entity_description.key,
128
  "on",
129
  )
@@ -133,7 +116,7 @@
133
  """Turn the device off."""
134
  await self.coordinator.api.set_switch_state(
135
  self._dev_id,
136
- self.device.get(MEMBERS),
137
  self.entity_description.key,
138
  "off",
139
  )
 
1
  """Plugwise Switch component for HomeAssistant."""
2
+
3
  from __future__ import annotations
4
 
5
  from dataclasses import dataclass
 
12
  SwitchEntity,
13
  SwitchEntityDescription,
14
  )
15
+ from homeassistant.const import EntityCategory
16
  from homeassistant.core import HomeAssistant, callback
17
  from homeassistant.helpers.entity_platform import AddEntitiesCallback
18
 
19
  from . import PlugwiseConfigEntry
 
 
 
 
 
 
 
 
 
20
  from .coordinator import PlugwiseDataUpdateCoordinator
21
  from .entity import PlugwiseEntity
22
  from .util import plugwise_command
 
29
  key: SwitchType
30
 
31
 
32
+ SWITCHES: tuple[PlugwiseSwitchEntityDescription, ...] = (
33
  PlugwiseSwitchEntityDescription(
34
+ key="dhw_cm_switch",
35
+ translation_key="dhw_cm_switch",
 
36
  entity_category=EntityCategory.CONFIG,
37
  ),
38
  PlugwiseSwitchEntityDescription(
39
+ key="lock",
40
+ translation_key="lock",
 
41
  entity_category=EntityCategory.CONFIG,
42
  ),
43
  PlugwiseSwitchEntityDescription(
44
+ key="relay",
45
+ translation_key="relay",
46
  device_class=SwitchDeviceClass.SWITCH,
47
  ),
48
  PlugwiseSwitchEntityDescription(
49
+ key="cooling_ena_switch",
50
+ translation_key="cooling_ena_switch",
51
+ name="Cooling",
52
  entity_category=EntityCategory.CONFIG,
53
  ),
54
  )
 
59
  entry: PlugwiseConfigEntry,
60
  async_add_entities: AddEntitiesCallback,
61
  ) -> None:
62
+ """Set up the Smile switches from a config entry."""
63
  coordinator = entry.runtime_data
64
 
65
  @callback
 
68
  if not coordinator.new_devices:
69
  return
70
 
71
+ async_add_entities(
72
+ PlugwiseSwitchEntity(coordinator, device_id, description)
73
+ for device_id in coordinator.new_devices
74
+ if (switches := coordinator.data.devices[device_id].get("switches"))
75
+ for description in SWITCHES
76
+ if description.key in switches
77
+ )
 
 
 
 
 
 
 
 
78
 
79
  _add_entities()
80
+ entry.async_on_unload(coordinator.async_add_listener(_add_entities))
81
 
82
 
83
  class PlugwiseSwitchEntity(PlugwiseEntity, SwitchEntity):
 
99
  @property
100
  def is_on(self) -> bool:
101
  """Return True if entity is on."""
102
+ return self.device["switches"][self.entity_description.key]
103
 
104
  @plugwise_command
105
  async def async_turn_on(self, **kwargs: Any) -> None:
106
  """Turn the device on."""
107
  await self.coordinator.api.set_switch_state(
108
  self._dev_id,
109
+ self.device.get("members"),
110
  self.entity_description.key,
111
  "on",
112
  )
 
116
  """Turn the device off."""
117
  await self.coordinator.api.set_switch_state(
118
  self._dev_id,
119
+ self.device.get("members"),
120
  self.entity_description.key,
121
  "off",
122
  )
/home/runner/work/progress/progress/clones/beta/{beta/custom_components → ha-core/homeassistant/components}/plugwise/util.py RENAMED
@@ -1,8 +1,7 @@
1
  """Utilities for Plugwise."""
2
- from __future__ import annotations
3
 
4
  from collections.abc import Awaitable, Callable, Coroutine
5
- from typing import Any, Concatenate, ParamSpec, TypeVar
6
 
7
  from plugwise.exceptions import PlugwiseException
8
 
@@ -10,13 +9,9 @@
10
 
11
  from .entity import PlugwiseEntity
12
 
13
- _PlugwiseEntityT = TypeVar("_PlugwiseEntityT", bound=PlugwiseEntity)
14
- _R = TypeVar("_R")
15
- _P = ParamSpec("_P")
16
 
17
-
18
- def plugwise_command(
19
- func: Callable[Concatenate[_PlugwiseEntityT, _P], Awaitable[_R]]
20
  ) -> Callable[Concatenate[_PlugwiseEntityT, _P], Coroutine[Any, Any, _R]]:
21
  """Decorate Plugwise calls that send commands/make changes to the device.
22
 
 
1
  """Utilities for Plugwise."""
 
2
 
3
  from collections.abc import Awaitable, Callable, Coroutine
4
+ from typing import Any, Concatenate
5
 
6
  from plugwise.exceptions import PlugwiseException
7
 
 
9
 
10
  from .entity import PlugwiseEntity
11
 
 
 
 
12
 
13
+ def plugwise_command[_PlugwiseEntityT: PlugwiseEntity, **_P, _R](
14
+ func: Callable[Concatenate[_PlugwiseEntityT, _P], Awaitable[_R]],
 
15
  ) -> Callable[Concatenate[_PlugwiseEntityT, _P], Coroutine[Any, Any, _R]]:
16
  """Decorate Plugwise calls that send commands/make changes to the device.
17