Diff to HTML by rtfpessoa

Files changed (16) hide show
  1. /home/runner/work/progress/progress/clones/beta/{beta/custom_components → ha-core/homeassistant/components}/plugwise/__init__.py +19 -65
  2. /home/runner/work/progress/progress/clones/beta/{beta/custom_components → ha-core/homeassistant/components}/plugwise/binary_sensor.py +38 -95
  3. /home/runner/work/progress/progress/clones/beta/{beta/custom_components → ha-core/homeassistant/components}/plugwise/button.py +10 -17
  4. /home/runner/work/progress/progress/clones/beta/{beta/custom_components → ha-core/homeassistant/components}/plugwise/climate.py +104 -127
  5. /home/runner/work/progress/progress/clones/beta/{beta/custom_components → ha-core/homeassistant/components}/plugwise/config_flow.py +32 -131
  6. /home/runner/work/progress/progress/clones/beta/{beta/custom_components → ha-core/homeassistant/components}/plugwise/const.py +33 -152
  7. /home/runner/work/progress/progress/clones/beta/{beta/custom_components → ha-core/homeassistant/components}/plugwise/coordinator.py +30 -48
  8. /home/runner/work/progress/progress/clones/beta/{beta/custom_components → ha-core/homeassistant/components}/plugwise/entity.py +13 -24
  9. /home/runner/work/progress/progress/clones/beta/{beta/custom_components → ha-core/homeassistant/components}/plugwise/icons.json +0 -3
  10. /home/runner/work/progress/progress/clones/beta/{beta/custom_components → ha-core/homeassistant/components}/plugwise/manifest.json +3 -3
  11. /home/runner/work/progress/progress/clones/beta/{beta/custom_components → ha-core/homeassistant/components}/plugwise/number.py +27 -55
  12. /home/runner/work/progress/progress/clones/beta/{beta/custom_components → ha-core/homeassistant/components}/plugwise/select.py +20 -46
  13. /home/runner/work/progress/progress/clones/beta/{beta/custom_components → ha-core/homeassistant/components}/plugwise/sensor.py +103 -172
  14. /home/runner/work/progress/progress/clones/beta/{beta/custom_components → ha-core/homeassistant/components}/plugwise/strings.json +47 -64
  15. /home/runner/work/progress/progress/clones/beta/{beta/custom_components → ha-core/homeassistant/components}/plugwise/switch.py +28 -55
  16. /home/runner/work/progress/progress/clones/beta/{beta/custom_components → ha-core/homeassistant/components}/plugwise/util.py +5 -10
/home/runner/work/progress/progress/clones/beta/{beta/custom_components → ha-core/homeassistant/components}/plugwise/__init__.py RENAMED
@@ -2,46 +2,21 @@
2
 
3
  from typing import Any
4
 
5
- from homeassistant.const import CONF_TIMEOUT, Platform
6
  from homeassistant.core import HomeAssistant, callback
7
  from homeassistant.helpers import device_registry as dr, entity_registry as er
8
- from homeassistant.helpers.typing import ConfigType
9
 
10
- from .const import (
11
- CONF_REFRESH_INTERVAL, # pw-beta options
12
- DOMAIN,
13
- LOGGER,
14
- PLATFORMS,
15
- )
16
  from .coordinator import PlugwiseConfigEntry, PlugwiseDataUpdateCoordinator
17
- from .services import async_setup_services
18
-
19
-
20
- async def async_setup(hass: HomeAssistant, _config: ConfigType) -> bool:
21
- """Set up my integration."""
22
-
23
- async_setup_services(hass)
24
-
25
- return True
26
 
27
 
28
  async def async_setup_entry(hass: HomeAssistant, entry: PlugwiseConfigEntry) -> bool:
29
- """Set up Plugwise from a config entry."""
30
  await er.async_migrate_entries(hass, entry.entry_id, async_migrate_entity_entry)
31
 
32
- cooldown = 1.5 # pw-beta frontend refresh-interval
33
- if (
34
- custom_refresh := entry.options.get(CONF_REFRESH_INTERVAL)
35
- ) is not None: # pragma: no cover
36
- cooldown = custom_refresh
37
- LOGGER.debug("DUC cooldown interval: %s", cooldown)
38
-
39
- coordinator = PlugwiseDataUpdateCoordinator(
40
- hass, cooldown, entry
41
- ) # pw-beta - cooldown, update_interval as extra
42
  await coordinator.async_config_entry_first_refresh()
43
-
44
- await async_migrate_sensor_entities(hass, coordinator)
45
 
46
  entry.runtime_data = coordinator
47
 
@@ -58,25 +33,20 @@
58
 
59
  await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
60
 
61
- entry.async_on_unload(entry.add_update_listener(update_listener)) # pw-beta options_flow
62
-
63
  return True
64
 
65
- async def update_listener(
66
- hass: HomeAssistant, entry: PlugwiseConfigEntry
67
- ) -> None: # pragma: no cover # pw-beta
68
- """Handle options update."""
69
- await hass.config_entries.async_reload(entry.entry_id)
70
 
71
  async def async_unload_entry(hass: HomeAssistant, entry: PlugwiseConfigEntry) -> bool:
72
- """Unload Plugwise."""
73
  return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
74
 
 
75
  @callback
76
  def async_migrate_entity_entry(entry: er.RegistryEntry) -> dict[str, Any] | None:
77
  """Migrate Plugwise entity entries.
78
 
79
- - Migrates old unique ID's from old binary_sensors and switches to the new unique ID's
 
80
  """
81
  if entry.domain == Platform.BINARY_SENSOR and entry.unique_id.endswith(
82
  "-slave_boiler_state"
@@ -98,17 +68,18 @@
98
  # No migration needed
99
  return None
100
 
101
- async def async_migrate_sensor_entities(
 
102
  hass: HomeAssistant,
103
  coordinator: PlugwiseDataUpdateCoordinator,
104
  ) -> None:
105
  """Migrate Sensors if needed."""
106
  ent_reg = er.async_get(hass)
107
 
108
- # Migrate opentherm_outdoor_temperature
109
  # to opentherm_outdoor_air_temperature sensor
110
  for device_id, device in coordinator.data.items():
111
- if device["dev_class"] != "heater_central":
112
  continue
113
 
114
  old_unique_id = f"{device_id}-outdoor_temperature"
@@ -116,27 +87,10 @@
116
  Platform.SENSOR, DOMAIN, old_unique_id
117
  ):
118
  new_unique_id = f"{device_id}-outdoor_air_temperature"
119
- # Upstream remove LOGGER debug
 
 
 
 
 
120
  ent_reg.async_update_entity(entity_id, new_unique_id=new_unique_id)
121
-
122
- # pw-beta only - revert adding CONF_TIMEOUT to config_entry in v0.53.3
123
- async def async_migrate_entry(hass: HomeAssistant, entry: PlugwiseConfigEntry) -> bool:
124
- """Migrate back to v1.1 config entry."""
125
- if entry.version > 1:
126
- # This means the user has downgraded from a future version
127
- return False #pragma: no cover
128
-
129
- if entry.version == 1 and entry.minor_version == 2:
130
- new_data = {**entry.data}
131
- new_data.pop(CONF_TIMEOUT)
132
- hass.config_entries.async_update_entry(
133
- entry, data=new_data, minor_version=1, version=1
134
- )
135
-
136
- LOGGER.debug(
137
- "Migration to version %s.%s successful",
138
- entry.version,
139
- entry.minor_version,
140
- )
141
-
142
- return True
 
2
 
3
  from typing import Any
4
 
5
+ from homeassistant.const import Platform
6
  from homeassistant.core import HomeAssistant, callback
7
  from homeassistant.helpers import device_registry as dr, entity_registry as er
 
8
 
9
+ from .const import DEV_CLASS, DOMAIN, LOGGER, PLATFORMS
 
 
 
 
 
10
  from .coordinator import PlugwiseConfigEntry, PlugwiseDataUpdateCoordinator
 
 
 
 
 
 
 
 
 
11
 
12
 
13
  async def async_setup_entry(hass: HomeAssistant, entry: PlugwiseConfigEntry) -> bool:
14
+ """Set up Plugwise components from a config entry."""
15
  await er.async_migrate_entries(hass, entry.entry_id, async_migrate_entity_entry)
16
 
17
+ coordinator = PlugwiseDataUpdateCoordinator(hass, entry)
 
 
 
 
 
 
 
 
 
18
  await coordinator.async_config_entry_first_refresh()
19
+ migrate_sensor_entities(hass, coordinator)
 
20
 
21
  entry.runtime_data = coordinator
22
 
 
33
 
34
  await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
35
 
 
 
36
  return True
37
 
 
 
 
 
 
38
 
39
  async def async_unload_entry(hass: HomeAssistant, entry: PlugwiseConfigEntry) -> bool:
40
+ """Unload the Plugwise components."""
41
  return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
42
 
43
+
44
  @callback
45
  def async_migrate_entity_entry(entry: er.RegistryEntry) -> dict[str, Any] | None:
46
  """Migrate Plugwise entity entries.
47
 
48
+ Migrates old unique ID's from old binary_sensors and
49
+ switches to the new unique ID's.
50
  """
51
  if entry.domain == Platform.BINARY_SENSOR and entry.unique_id.endswith(
52
  "-slave_boiler_state"
 
68
  # No migration needed
69
  return None
70
 
71
+
72
+ def migrate_sensor_entities(
73
  hass: HomeAssistant,
74
  coordinator: PlugwiseDataUpdateCoordinator,
75
  ) -> None:
76
  """Migrate Sensors if needed."""
77
  ent_reg = er.async_get(hass)
78
 
79
+ # Migrating opentherm_outdoor_temperature
80
  # to opentherm_outdoor_air_temperature sensor
81
  for device_id, device in coordinator.data.items():
82
+ if device[DEV_CLASS] != "heater_central":
83
  continue
84
 
85
  old_unique_id = f"{device_id}-outdoor_temperature"
 
87
  Platform.SENSOR, DOMAIN, old_unique_id
88
  ):
89
  new_unique_id = f"{device_id}-outdoor_air_temperature"
90
+ LOGGER.debug(
91
+ "Migrating entity %s from old unique ID '%s' to new unique ID '%s'",
92
+ entity_id,
93
+ old_unique_id,
94
+ new_unique_id,
95
+ )
96
  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
@@ -2,13 +2,10 @@
2
 
3
  from collections.abc import Mapping
4
  from dataclasses import dataclass
5
- from typing import Any
6
 
7
  from plugwise.constants import BinarySensorType
8
 
9
- from homeassistant.components import (
10
- persistent_notification, # pw-beta Plugwise notifications
11
- )
12
  from homeassistant.components.binary_sensor import (
13
  BinarySensorDeviceClass,
14
  BinarySensorEntity,
@@ -18,26 +15,11 @@
18
  from homeassistant.core import HomeAssistant, callback
19
  from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
20
 
21
- from .const import (
22
- BATTERY_STATE,
23
- BINARY_SENSORS,
24
- COMPRESSOR_STATE,
25
- COOLING_ENABLED,
26
- COOLING_STATE,
27
- DHW_STATE,
28
- DOMAIN,
29
- FLAME_STATE,
30
- HEATING_STATE,
31
- LOGGER, # pw-beta
32
- PLUGWISE_NOTIFICATION,
33
- SECONDARY_BOILER_STATE,
34
- SEVERITIES,
35
- )
36
-
37
- # Upstream
38
  from .coordinator import PlugwiseConfigEntry, PlugwiseDataUpdateCoordinator
39
  from .entity import PlugwiseEntity
40
 
 
 
41
  # Coordinator is used to centralize the data updates
42
  PARALLEL_UPDATES = 0
43
 
@@ -49,51 +31,50 @@
49
  key: BinarySensorType
50
 
51
 
52
- # Upstream PLUGWISE_BINARY_SENSORS
53
- PLUGWISE_BINARY_SENSORS: tuple[PlugwiseBinarySensorEntityDescription, ...] = (
54
  PlugwiseBinarySensorEntityDescription(
55
- key=BATTERY_STATE,
56
  device_class=BinarySensorDeviceClass.BATTERY,
57
  entity_category=EntityCategory.DIAGNOSTIC,
58
  ),
59
  PlugwiseBinarySensorEntityDescription(
60
- key=COMPRESSOR_STATE,
61
- translation_key=COMPRESSOR_STATE,
62
  entity_category=EntityCategory.DIAGNOSTIC,
63
  ),
64
  PlugwiseBinarySensorEntityDescription(
65
- key=COOLING_ENABLED,
66
- translation_key=COOLING_ENABLED,
67
  entity_category=EntityCategory.DIAGNOSTIC,
68
  ),
69
  PlugwiseBinarySensorEntityDescription(
70
- key=DHW_STATE,
71
- translation_key=DHW_STATE,
72
  entity_category=EntityCategory.DIAGNOSTIC,
73
  ),
74
  PlugwiseBinarySensorEntityDescription(
75
- key=FLAME_STATE,
76
- translation_key=FLAME_STATE,
77
  entity_category=EntityCategory.DIAGNOSTIC,
78
  ),
79
  PlugwiseBinarySensorEntityDescription(
80
- key=HEATING_STATE,
81
- translation_key=HEATING_STATE,
82
  entity_category=EntityCategory.DIAGNOSTIC,
83
  ),
84
  PlugwiseBinarySensorEntityDescription(
85
- key=COOLING_STATE,
86
- translation_key=COOLING_STATE,
87
  entity_category=EntityCategory.DIAGNOSTIC,
88
  ),
89
  PlugwiseBinarySensorEntityDescription(
90
- key=SECONDARY_BOILER_STATE,
91
- translation_key=SECONDARY_BOILER_STATE,
92
  entity_category=EntityCategory.DIAGNOSTIC,
93
  ),
94
  PlugwiseBinarySensorEntityDescription(
95
- key=PLUGWISE_NOTIFICATION,
96
- translation_key=PLUGWISE_NOTIFICATION,
97
  entity_category=EntityCategory.DIAGNOSTIC,
98
  ),
99
  )
@@ -104,7 +85,7 @@
104
  entry: PlugwiseConfigEntry,
105
  async_add_entities: AddConfigEntryEntitiesCallback,
106
  ) -> None:
107
- """Set up Plugwise binary_sensors from a config entry."""
108
  coordinator = entry.runtime_data
109
 
110
  @callback
@@ -113,40 +94,20 @@
113
  if not coordinator.new_devices:
114
  return
115
 
116
- # Upstream consts to HA
117
- # async_add_entities(
118
- # PlugwiseBinarySensorEntity(coordinator, device_id, description)
119
- # for device_id in coordinator.new_devices
120
- # if (
121
- # binary_sensors := coordinator.data.devices[device_id].get(
122
- # BINARY_SENSORS
123
- # )
124
- # )
125
- # for description in PLUGWISE_BINARY_SENSORS
126
- # if description.key in binary_sensors
127
- # )
128
-
129
- # pw-beta alternative for debugging
130
- entities: list[PlugwiseBinarySensorEntity] = []
131
- for device_id in coordinator.new_devices:
132
- device = coordinator.data[device_id]
133
- if not (binary_sensors := device.get(BINARY_SENSORS)):
134
- continue
135
- for description in PLUGWISE_BINARY_SENSORS:
136
- if description.key not in binary_sensors:
137
- continue
138
- entities.append(PlugwiseBinarySensorEntity(coordinator, device_id, description))
139
- LOGGER.debug(
140
- "Add %s %s binary sensor", device["name"], description.translation_key or description.key
141
- )
142
- async_add_entities(entities)
143
 
144
  _add_entities()
145
  entry.async_on_unload(coordinator.async_add_listener(_add_entities))
146
 
147
 
148
  class PlugwiseBinarySensorEntity(PlugwiseEntity, BinarySensorEntity):
149
- """Set up Plugwise binary_sensors from a config entry."""
150
 
151
  entity_description: PlugwiseBinarySensorEntityDescription
152
 
@@ -160,46 +121,28 @@
160
  super().__init__(coordinator, device_id)
161
  self.entity_description = description
162
  self._attr_unique_id = f"{device_id}-{description.key}"
163
- self._notification: dict[str, str] = {} # pw-beta
164
 
165
  @property
166
- def is_on(self) -> bool | None:
 
167
  """Return true if the binary sensor is on."""
168
- # pw-beta: show Plugwise notifications as HA persistent notifications
169
- if self._notification:
170
- for notify_id, message in self._notification.items():
171
- persistent_notification.async_create(
172
- self.hass, message, "Plugwise Notification:", f"{DOMAIN}.{notify_id}"
173
- )
174
-
175
- return self.device.get(BINARY_SENSORS, {}).get(self.entity_description.key)
176
 
177
  @property
 
178
  def extra_state_attributes(self) -> Mapping[str, Any] | None:
179
  """Return entity specific state attributes."""
180
- if self.entity_description.key != PLUGWISE_NOTIFICATION: # Upstream const
181
  return None
182
 
183
- # pw-beta adjustment with attrs is to only represent severities *with* content
184
- # not all severities including those without content as empty lists
185
- attrs: dict[str, list[str]] = {} # pw-beta Re-evaluate against Core
186
- self._notification = {} # pw-beta
187
  gateway_id = self.coordinator.api.gateway_id
188
  if notify := self.coordinator.data[gateway_id]["notifications"]:
189
- for notify_id, details in notify.items(): # pw-beta uses notify_id
190
  for msg_type, msg in details.items():
191
  msg_type = msg_type.lower()
192
  if msg_type not in SEVERITIES:
193
- msg_type = "other" # pragma: no cover
194
-
195
- if (
196
- f"{msg_type}_msg" not in attrs
197
- ): # pw-beta Re-evaluate against Core
198
- attrs[f"{msg_type}_msg"] = []
199
  attrs[f"{msg_type}_msg"].append(msg)
200
 
201
- self._notification[
202
- notify_id
203
- ] = f"{msg_type.title()}: {msg}" # pw-beta
204
-
205
  return attrs
 
2
 
3
  from collections.abc import Mapping
4
  from dataclasses import dataclass
5
+ from typing import Any, override
6
 
7
  from plugwise.constants import BinarySensorType
8
 
 
 
 
9
  from homeassistant.components.binary_sensor import (
10
  BinarySensorDeviceClass,
11
  BinarySensorEntity,
 
15
  from homeassistant.core import HomeAssistant, callback
16
  from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
17
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  from .coordinator import PlugwiseConfigEntry, PlugwiseDataUpdateCoordinator
19
  from .entity import PlugwiseEntity
20
 
21
+ SEVERITIES = ["other", "info", "warning", "error"]
22
+
23
  # Coordinator is used to centralize the data updates
24
  PARALLEL_UPDATES = 0
25
 
 
31
  key: BinarySensorType
32
 
33
 
34
+ BINARY_SENSORS: tuple[PlugwiseBinarySensorEntityDescription, ...] = (
 
35
  PlugwiseBinarySensorEntityDescription(
36
+ key="low_battery",
37
  device_class=BinarySensorDeviceClass.BATTERY,
38
  entity_category=EntityCategory.DIAGNOSTIC,
39
  ),
40
  PlugwiseBinarySensorEntityDescription(
41
+ key="compressor_state",
42
+ translation_key="compressor_state",
43
  entity_category=EntityCategory.DIAGNOSTIC,
44
  ),
45
  PlugwiseBinarySensorEntityDescription(
46
+ key="cooling_enabled",
47
+ translation_key="cooling_enabled",
48
  entity_category=EntityCategory.DIAGNOSTIC,
49
  ),
50
  PlugwiseBinarySensorEntityDescription(
51
+ key="dhw_state",
52
+ translation_key="dhw_state",
53
  entity_category=EntityCategory.DIAGNOSTIC,
54
  ),
55
  PlugwiseBinarySensorEntityDescription(
56
+ key="flame_state",
57
+ translation_key="flame_state",
58
  entity_category=EntityCategory.DIAGNOSTIC,
59
  ),
60
  PlugwiseBinarySensorEntityDescription(
61
+ key="heating_state",
62
+ translation_key="heating_state",
63
  entity_category=EntityCategory.DIAGNOSTIC,
64
  ),
65
  PlugwiseBinarySensorEntityDescription(
66
+ key="cooling_state",
67
+ translation_key="cooling_state",
68
  entity_category=EntityCategory.DIAGNOSTIC,
69
  ),
70
  PlugwiseBinarySensorEntityDescription(
71
+ key="secondary_boiler_state",
72
+ translation_key="secondary_boiler_state",
73
  entity_category=EntityCategory.DIAGNOSTIC,
74
  ),
75
  PlugwiseBinarySensorEntityDescription(
76
+ key="plugwise_notification",
77
+ translation_key="plugwise_notification",
78
  entity_category=EntityCategory.DIAGNOSTIC,
79
  ),
80
  )
 
85
  entry: PlugwiseConfigEntry,
86
  async_add_entities: AddConfigEntryEntitiesCallback,
87
  ) -> None:
88
+ """Set up the Smile binary_sensors from a config entry."""
89
  coordinator = entry.runtime_data
90
 
91
  @callback
 
94
  if not coordinator.new_devices:
95
  return
96
 
97
+ async_add_entities(
98
+ PlugwiseBinarySensorEntity(coordinator, device_id, description)
99
+ for device_id in coordinator.new_devices
100
+ if (binary_sensors := coordinator.data[device_id].get("binary_sensors"))
101
+ for description in BINARY_SENSORS
102
+ if description.key in binary_sensors
103
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
104
 
105
  _add_entities()
106
  entry.async_on_unload(coordinator.async_add_listener(_add_entities))
107
 
108
 
109
  class PlugwiseBinarySensorEntity(PlugwiseEntity, BinarySensorEntity):
110
+ """Represent Smile Binary Sensors."""
111
 
112
  entity_description: PlugwiseBinarySensorEntityDescription
113
 
 
121
  super().__init__(coordinator, device_id)
122
  self.entity_description = description
123
  self._attr_unique_id = f"{device_id}-{description.key}"
 
124
 
125
  @property
126
+ @override
127
+ def is_on(self) -> bool:
128
  """Return true if the binary sensor is on."""
129
+ return self.device["binary_sensors"][self.entity_description.key]
 
 
 
 
 
 
 
130
 
131
  @property
132
+ @override
133
  def extra_state_attributes(self) -> Mapping[str, Any] | None:
134
  """Return entity specific state attributes."""
135
+ if self.entity_description.key != "plugwise_notification":
136
  return None
137
 
138
+ attrs: dict[str, list[str]] = {f"{severity}_msg": [] for severity in SEVERITIES}
 
 
 
139
  gateway_id = self.coordinator.api.gateway_id
140
  if notify := self.coordinator.data[gateway_id]["notifications"]:
141
+ for details in notify.values():
142
  for msg_type, msg in details.items():
143
  msg_type = msg_type.lower()
144
  if msg_type not in SEVERITIES:
145
+ msg_type = "other"
 
 
 
 
 
146
  attrs[f"{msg_type}_msg"].append(msg)
147
 
 
 
 
 
148
  return attrs
/home/runner/work/progress/progress/clones/beta/{beta/custom_components → ha-core/homeassistant/components}/plugwise/button.py RENAMED
@@ -1,14 +1,13 @@
1
  """Plugwise Button component for Home Assistant."""
2
 
 
 
3
  from homeassistant.components.button import ButtonDeviceClass, ButtonEntity
4
  from homeassistant.const import EntityCategory
5
  from homeassistant.core import HomeAssistant
6
  from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
7
 
8
- from .const import (
9
- LOGGER, # pw-betea
10
- REBOOT,
11
- )
12
  from .coordinator import PlugwiseConfigEntry, PlugwiseDataUpdateCoordinator
13
  from .entity import PlugwiseEntity
14
  from .util import plugwise_command
@@ -21,21 +20,14 @@
21
  entry: PlugwiseConfigEntry,
22
  async_add_entities: AddConfigEntryEntitiesCallback,
23
  ) -> None:
24
- """Set up Plugwise buttons from a config entry."""
25
  coordinator = entry.runtime_data
26
 
27
- # async_add_entities(
28
- # PlugwiseButtonEntity(coordinator, device_id)
29
- # for device_id in coordinator.data.devices
30
- # if device_id == gateway[GATEWAY_ID] and REBOOT in gateway
31
- # )
32
- # pw-beta alternative for debugging
33
- entities: list[PlugwiseButtonEntity] = []
34
- for device_id, device in coordinator.data.items():
35
- if device_id == coordinator.api.gateway_id and coordinator.api.reboot:
36
- entities.append(PlugwiseButtonEntity(coordinator, device_id))
37
- LOGGER.debug("Add %s reboot button", device["name"])
38
- async_add_entities(entities)
39
 
40
 
41
  class PlugwiseButtonEntity(PlugwiseEntity, ButtonEntity):
@@ -55,6 +47,7 @@
55
  self._attr_unique_id = f"{device_id}-reboot"
56
 
57
  @plugwise_command
 
58
  async def async_press(self) -> None:
59
  """Triggers the Plugwise button press service."""
60
  await self.coordinator.api.reboot_gateway()
 
1
  """Plugwise Button component for Home Assistant."""
2
 
3
+ from typing import override
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 AddConfigEntryEntitiesCallback
9
 
10
+ from .const import REBOOT
 
 
 
11
  from .coordinator import PlugwiseConfigEntry, PlugwiseDataUpdateCoordinator
12
  from .entity import PlugwiseEntity
13
  from .util import plugwise_command
 
20
  entry: PlugwiseConfigEntry,
21
  async_add_entities: AddConfigEntryEntitiesCallback,
22
  ) -> None:
23
+ """Set up the Plugwise buttons from a ConfigEntry."""
24
  coordinator = entry.runtime_data
25
 
26
+ async_add_entities(
27
+ PlugwiseButtonEntity(coordinator, device_id)
28
+ for device_id in coordinator.data
29
+ if device_id == coordinator.api.gateway_id and coordinator.api.reboot
30
+ )
 
 
 
 
 
 
 
31
 
32
 
33
  class PlugwiseButtonEntity(PlugwiseEntity, ButtonEntity):
 
47
  self._attr_unique_id = f"{device_id}-reboot"
48
 
49
  @plugwise_command
50
+ @override
51
  async def async_press(self) -> None:
52
  """Triggers the Plugwise button press service."""
53
  await self.coordinator.api.reboot_gateway()
/home/runner/work/progress/progress/clones/beta/{beta/custom_components → ha-core/homeassistant/components}/plugwise/climate.py RENAMED
@@ -1,7 +1,7 @@
1
  """Plugwise Climate component for Home Assistant."""
2
 
3
  from dataclasses import asdict, dataclass
4
- from typing import Any
5
 
6
  from homeassistant.components.climate import (
7
  ATTR_HVAC_MODE,
@@ -12,39 +12,13 @@
12
  HVACAction,
13
  HVACMode,
14
  )
15
- from homeassistant.const import (
16
- ATTR_NAME,
17
- ATTR_TEMPERATURE,
18
- STATE_OFF,
19
- STATE_ON,
20
- UnitOfTemperature,
21
- )
22
  from homeassistant.core import HomeAssistant, callback
23
  from homeassistant.exceptions import HomeAssistantError
24
  from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
25
  from homeassistant.helpers.restore_state import ExtraStoredData, RestoreEntity
26
 
27
- from .const import (
28
- ACTIVE_PRESET,
29
- AVAILABLE_SCHEDULES,
30
- CLIMATE_MODE,
31
- CONTROL_STATE,
32
- DEV_CLASS,
33
- DOMAIN,
34
- LOCATION,
35
- LOGGER,
36
- LOWER_BOUND,
37
- MASTER_THERMOSTATS,
38
- REGULATION_MODES,
39
- RESOLUTION,
40
- SELECT_REGULATION_MODE,
41
- SENSORS,
42
- TARGET_TEMP,
43
- TARGET_TEMP_HIGH,
44
- TARGET_TEMP_LOW,
45
- THERMOSTAT,
46
- UPPER_BOUND,
47
- )
48
  from .coordinator import PlugwiseConfigEntry, PlugwiseDataUpdateCoordinator
49
  from .entity import PlugwiseEntity
50
  from .util import plugwise_command
@@ -53,42 +27,6 @@
53
  PARALLEL_UPDATES = 0
54
 
55
 
56
- async def async_setup_entry(
57
- hass: HomeAssistant,
58
- entry: PlugwiseConfigEntry,
59
- async_add_entities: AddConfigEntryEntitiesCallback,
60
- ) -> None:
61
- """Set up Plugwise thermostats from a config entry."""
62
- coordinator = entry.runtime_data
63
-
64
- @callback
65
- def _add_entities() -> None:
66
- """Add Entities during init and runtime."""
67
- if not coordinator.new_devices:
68
- return
69
-
70
- entities: list[PlugwiseClimateEntity] = []
71
- gateway_name = coordinator.api.smile.name
72
- for device_id in coordinator.new_devices:
73
- device = coordinator.data[device_id]
74
- if gateway_name == "Adam":
75
- if device[DEV_CLASS] == "climate":
76
- entities.append(
77
- PlugwiseClimateEntity(coordinator, device_id)
78
- )
79
- LOGGER.debug("Add climate %s", device[ATTR_NAME])
80
- elif device[DEV_CLASS] in MASTER_THERMOSTATS:
81
- entities.append(
82
- PlugwiseClimateEntity(coordinator, device_id)
83
- )
84
- LOGGER.debug("Add climate %s", device[ATTR_NAME])
85
-
86
- async_add_entities(entities)
87
-
88
- _add_entities()
89
- entry.async_on_unload(coordinator.async_add_listener(_add_entities))
90
-
91
-
92
  def _check_for_schedule(active: bool, last_active: str | None) -> None:
93
  """Raise a HAError when no thermostat schedule has been set."""
94
  if not active and last_active is None:
@@ -105,19 +43,57 @@
105
  last_active_schedule: str | None
106
  previous_action_mode: str | None
107
 
 
108
  def as_dict(self) -> dict[str, Any]:
109
  """Return a dict representation of the text data."""
110
  return asdict(self)
111
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
 
113
  class PlugwiseClimateEntity(PlugwiseEntity, ClimateEntity, RestoreEntity):
114
  """Representation of a Plugwise thermostat."""
115
 
116
- _attr_has_entity_name = True
117
  _attr_name = None
118
  _attr_temperature_unit = UnitOfTemperature.CELSIUS
119
  _attr_translation_key = DOMAIN
120
- _enable_turn_on_off_backwards_compatibility = False
121
 
122
  def __init__(
123
  self,
@@ -126,30 +102,20 @@
126
  ) -> None:
127
  """Set up the Plugwise API."""
128
  super().__init__(coordinator, device_id)
 
129
 
130
  self._api = coordinator.api
131
  gateway_id: str = self._api.gateway_id
132
  self._gateway_data = coordinator.data[gateway_id]
133
  self._last_active_schedule: str | None = None
134
  self._location = device_id
135
- if (location := self.device.get(LOCATION)) is not None:
136
  self._location = location
137
  self._previous_action_mode = HVACAction.HEATING.value
138
 
139
- self._attr_max_temp = min(self.device.get(THERMOSTAT, {}).get(UPPER_BOUND, 35.0), 35.0)
140
- self._attr_min_temp = self.device.get(THERMOSTAT, {}).get(LOWER_BOUND, 0.0)
141
- # Ensure we don't drop below 0.1
142
- self._attr_target_temperature_step = max(
143
- self.device.get(THERMOSTAT, {}).get(RESOLUTION, 0.5), 0.1
144
- )
145
- self._attr_unique_id = f"{device_id}-climate"
146
-
147
  # Determine supported features
148
  self._attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE
149
- if (
150
- self._api.cooling_present
151
- and self._api.smile.name != "Adam"
152
- ):
153
  self._attr_supported_features = (
154
  ClimateEntityFeature.TARGET_TEMPERATURE_RANGE
155
  )
@@ -157,91 +123,100 @@
157
  self._attr_supported_features |= (
158
  ClimateEntityFeature.TURN_OFF | ClimateEntityFeature.TURN_ON
159
  )
160
- if presets := self.device.get("preset_modes", None): # can be NONE
161
  self._attr_supported_features |= ClimateEntityFeature.PRESET_MODE
162
- self._attr_preset_modes = presets
 
 
 
 
 
 
 
163
 
 
164
  async def async_added_to_hass(self) -> None:
165
  """Run when entity about to be added."""
 
166
 
167
- extra_data = await self.async_get_last_extra_data()
168
- if extra_data is not None:
169
- data = extra_data.as_dict()
170
- self._last_active_schedule = data.get("last_active_schedule")
 
171
  self._previous_action_mode = (
172
- data.get("previous_action_mode") or HVACAction.HEATING.value
173
  )
174
 
175
- await super().async_added_to_hass()
176
-
177
  @property
 
178
  def extra_restore_state_data(self) -> PlugwiseClimateExtraStoredData:
179
  """Return text specific state data to be restored."""
180
  return PlugwiseClimateExtraStoredData(
181
- self._last_active_schedule,
182
- self._previous_action_mode,
183
  )
184
 
185
  @property
 
186
  def current_temperature(self) -> float | None:
187
  """Return the current temperature."""
188
- return self.device.get(SENSORS, {}).get(ATTR_TEMPERATURE)
189
 
190
  @property
191
- def target_temperature(self) -> float | None:
 
192
  """Return the temperature we try to reach.
193
 
194
  Connected to the HVACMode combination of AUTO-HEAT.
195
  """
196
 
197
- return self.device.get(THERMOSTAT, {}).get(TARGET_TEMP)
198
 
199
  @property
200
- def target_temperature_high(self) -> float | None:
 
201
  """Return the temperature we try to reach in case of cooling.
202
 
203
  Connected to the HVACMode combination of AUTO-HEAT_COOL.
204
  """
205
- return self.device.get(THERMOSTAT, {}).get(TARGET_TEMP_HIGH)
206
 
207
  @property
208
- def target_temperature_low(self) -> float | None:
 
209
  """Return the heating temperature we try to reach in case of heating.
210
 
211
  Connected to the HVACMode combination AUTO-HEAT_COOL.
212
  """
213
- return self.device.get(THERMOSTAT, {}).get(TARGET_TEMP_LOW)
214
 
215
  @property
 
216
  def hvac_mode(self) -> HVACMode:
217
  """Return HVAC operation ie. auto, cool, heat, heat_cool, or off mode."""
218
- mode = self.device.get(CLIMATE_MODE)
219
- if mode is None:
220
- return HVACMode.HEAT # pragma: no cover
221
- try:
222
- hvac = HVACMode(mode)
223
- except ValueError: # pragma: no cover
224
- return HVACMode.HEAT # pragma: no cover
225
- if hvac not in self.hvac_modes:
226
- return HVACMode.HEAT # pragma: no cover
227
-
228
- return hvac
229
 
230
  @property
 
231
  def hvac_modes(self) -> list[HVACMode]:
232
  """Return a list of available HVACModes."""
233
  hvac_modes: list[HVACMode] = []
234
- if REGULATION_MODES in self._gateway_data:
235
  hvac_modes.append(HVACMode.OFF)
236
 
237
- if self.device.get(AVAILABLE_SCHEDULES, []):
238
  hvac_modes.append(HVACMode.AUTO)
239
 
240
  if self._api.cooling_present:
241
- if REGULATION_MODES in self._gateway_data:
242
- if "heating" in self._gateway_data[REGULATION_MODES]:
243
  hvac_modes.append(HVACMode.HEAT)
244
- if "cooling" in self._gateway_data[REGULATION_MODES]:
245
  hvac_modes.append(HVACMode.COOL)
246
  else:
247
  hvac_modes.append(HVACMode.HEAT_COOL)
@@ -251,40 +226,41 @@
251
  return hvac_modes
252
 
253
  @property
254
- def hvac_action(self) -> HVACAction: # pw-beta add to Core
 
255
  """Return the current running hvac operation if supported."""
256
  # Keep track of the previous hvac_action mode.
257
  # When no cooling available, _previous_action_mode is always heating
258
  if (
259
- REGULATION_MODES in self._gateway_data
260
- and HVACAction.COOLING.value in self._gateway_data[REGULATION_MODES]
261
  ):
262
- mode = self._gateway_data[SELECT_REGULATION_MODE]
263
  if mode in (HVACAction.COOLING.value, HVACAction.HEATING.value):
264
  self._previous_action_mode = mode
265
 
266
- if (action := self.device.get(CONTROL_STATE)) is not None:
267
  return HVACAction(action)
268
 
269
  return HVACAction.IDLE
270
 
271
  @property
 
272
  def preset_mode(self) -> str | None:
273
  """Return the current preset mode."""
274
- return self.device.get(ACTIVE_PRESET)
275
 
276
  @plugwise_command
 
277
  async def async_set_temperature(self, **kwargs: Any) -> None:
278
  """Set new target temperature."""
279
  data: dict[str, Any] = {}
280
  if ATTR_TEMPERATURE in kwargs:
281
- data[TARGET_TEMP] = kwargs.get(ATTR_TEMPERATURE)
282
  if ATTR_TARGET_TEMP_HIGH in kwargs:
283
- data[TARGET_TEMP_HIGH] = kwargs.get(ATTR_TARGET_TEMP_HIGH)
284
  if ATTR_TARGET_TEMP_LOW in kwargs:
285
- data[TARGET_TEMP_LOW] = kwargs.get(ATTR_TARGET_TEMP_LOW)
286
-
287
- # Upstream removed input-valid check
288
 
289
  if mode := kwargs.get(ATTR_HVAC_MODE):
290
  await self.async_set_hvac_mode(mode)
@@ -292,7 +268,7 @@
292
  await self._api.set_temperature(self._location, data)
293
 
294
  def _regulation_mode_for_hvac(self, hvac_mode: HVACMode) -> str:
295
- """Return the API regulation value for a manual HVAC mode.
296
 
297
  The function inputs are limited to the HVACModes HEAT and COOL.
298
  """
@@ -303,9 +279,9 @@
303
  return mode
304
 
305
  @plugwise_command
 
306
  async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
307
  """Set the HVAC mode (off, heat, cool, heat_cool, or auto/schedule)."""
308
-
309
  # Early exit if no mode change
310
  if hvac_mode == self.hvac_mode:
311
  return
@@ -353,6 +329,7 @@
353
  await self._api.set_schedule_state(self._location, STATE_ON, desired_schedule)
354
 
355
  @plugwise_command
 
356
  async def async_set_preset_mode(self, preset_mode: str) -> None:
357
  """Set the preset mode."""
358
  await self._api.set_preset(self._location, preset_mode)
 
1
  """Plugwise Climate component for Home Assistant."""
2
 
3
  from dataclasses import asdict, dataclass
4
+ from typing import Any, override
5
 
6
  from homeassistant.components.climate import (
7
  ATTR_HVAC_MODE,
 
12
  HVACAction,
13
  HVACMode,
14
  )
15
+ from homeassistant.const import ATTR_TEMPERATURE, STATE_OFF, STATE_ON, UnitOfTemperature
 
 
 
 
 
 
16
  from homeassistant.core import HomeAssistant, callback
17
  from homeassistant.exceptions import HomeAssistantError
18
  from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
19
  from homeassistant.helpers.restore_state import ExtraStoredData, RestoreEntity
20
 
21
+ from .const import DOMAIN, MASTER_THERMOSTATS
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  from .coordinator import PlugwiseConfigEntry, PlugwiseDataUpdateCoordinator
23
  from .entity import PlugwiseEntity
24
  from .util import plugwise_command
 
27
  PARALLEL_UPDATES = 0
28
 
29
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  def _check_for_schedule(active: bool, last_active: str | None) -> None:
31
  """Raise a HAError when no thermostat schedule has been set."""
32
  if not active and last_active is None:
 
43
  last_active_schedule: str | None
44
  previous_action_mode: str | None
45
 
46
+ @override
47
  def as_dict(self) -> dict[str, Any]:
48
  """Return a dict representation of the text data."""
49
  return asdict(self)
50
 
51
+ @classmethod
52
+ def from_dict(cls, restored: dict[str, Any]) -> PlugwiseClimateExtraStoredData:
53
+ """Initialize a stored data object from a dict."""
54
+ return cls(
55
+ last_active_schedule=restored.get("last_active_schedule"),
56
+ previous_action_mode=restored.get("previous_action_mode"),
57
+ )
58
+
59
+
60
+ async def async_setup_entry(
61
+ hass: HomeAssistant,
62
+ entry: PlugwiseConfigEntry,
63
+ async_add_entities: AddConfigEntryEntitiesCallback,
64
+ ) -> None:
65
+ """Set up the Smile Thermostats from a config entry."""
66
+ coordinator = entry.runtime_data
67
+
68
+ @callback
69
+ def _add_entities() -> None:
70
+ """Add Entities."""
71
+ if not coordinator.new_devices:
72
+ return
73
+
74
+ if coordinator.api.smile.name == "Adam":
75
+ async_add_entities(
76
+ PlugwiseClimateEntity(coordinator, device_id)
77
+ for device_id in coordinator.new_devices
78
+ if coordinator.data[device_id]["dev_class"] == "climate"
79
+ )
80
+ else:
81
+ async_add_entities(
82
+ PlugwiseClimateEntity(coordinator, device_id)
83
+ for device_id in coordinator.new_devices
84
+ if coordinator.data[device_id]["dev_class"] in MASTER_THERMOSTATS
85
+ )
86
+
87
+ _add_entities()
88
+ entry.async_on_unload(coordinator.async_add_listener(_add_entities))
89
+
90
 
91
  class PlugwiseClimateEntity(PlugwiseEntity, ClimateEntity, RestoreEntity):
92
  """Representation of a Plugwise thermostat."""
93
 
 
94
  _attr_name = None
95
  _attr_temperature_unit = UnitOfTemperature.CELSIUS
96
  _attr_translation_key = DOMAIN
 
97
 
98
  def __init__(
99
  self,
 
102
  ) -> None:
103
  """Set up the Plugwise API."""
104
  super().__init__(coordinator, device_id)
105
+ self._attr_unique_id = f"{device_id}-climate"
106
 
107
  self._api = coordinator.api
108
  gateway_id: str = self._api.gateway_id
109
  self._gateway_data = coordinator.data[gateway_id]
110
  self._last_active_schedule: str | None = None
111
  self._location = device_id
112
+ if (location := self.device.get("location")) is not None:
113
  self._location = location
114
  self._previous_action_mode = HVACAction.HEATING.value
115
 
 
 
 
 
 
 
 
 
116
  # Determine supported features
117
  self._attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE
118
+ if self._api.cooling_present and self._api.smile.name != "Adam":
 
 
 
119
  self._attr_supported_features = (
120
  ClimateEntityFeature.TARGET_TEMPERATURE_RANGE
121
  )
 
123
  self._attr_supported_features |= (
124
  ClimateEntityFeature.TURN_OFF | ClimateEntityFeature.TURN_ON
125
  )
126
+ if presets := self.device.get("preset_modes"):
127
  self._attr_supported_features |= ClimateEntityFeature.PRESET_MODE
128
+ self._attr_preset_modes = presets
129
+
130
+ self._attr_min_temp = self.device["thermostat"]["lower_bound"]
131
+ self._attr_max_temp = min(self.device["thermostat"]["upper_bound"], 35.0)
132
+ # Ensure we don't drop below 0.1
133
+ self._attr_target_temperature_step = max(
134
+ self.device["thermostat"]["resolution"], 0.1
135
+ )
136
 
137
+ @override
138
  async def async_added_to_hass(self) -> None:
139
  """Run when entity about to be added."""
140
+ await super().async_added_to_hass()
141
 
142
+ if extra_data := await self.async_get_last_extra_data():
143
+ plugwise_extra_data = PlugwiseClimateExtraStoredData.from_dict(
144
+ extra_data.as_dict()
145
+ )
146
+ self._last_active_schedule = plugwise_extra_data.last_active_schedule
147
  self._previous_action_mode = (
148
+ plugwise_extra_data.previous_action_mode or HVACAction.HEATING.value
149
  )
150
 
 
 
151
  @property
152
+ @override
153
  def extra_restore_state_data(self) -> PlugwiseClimateExtraStoredData:
154
  """Return text specific state data to be restored."""
155
  return PlugwiseClimateExtraStoredData(
156
+ last_active_schedule=self._last_active_schedule,
157
+ previous_action_mode=self._previous_action_mode,
158
  )
159
 
160
  @property
161
+ @override
162
  def current_temperature(self) -> float | None:
163
  """Return the current temperature."""
164
+ return self.device["sensors"].get("temperature")
165
 
166
  @property
167
+ @override
168
+ def target_temperature(self) -> float:
169
  """Return the temperature we try to reach.
170
 
171
  Connected to the HVACMode combination of AUTO-HEAT.
172
  """
173
 
174
+ return self.device["thermostat"]["setpoint"]
175
 
176
  @property
177
+ @override
178
+ def target_temperature_high(self) -> float:
179
  """Return the temperature we try to reach in case of cooling.
180
 
181
  Connected to the HVACMode combination of AUTO-HEAT_COOL.
182
  """
183
+ return self.device["thermostat"]["setpoint_high"]
184
 
185
  @property
186
+ @override
187
+ def target_temperature_low(self) -> float:
188
  """Return the heating temperature we try to reach in case of heating.
189
 
190
  Connected to the HVACMode combination AUTO-HEAT_COOL.
191
  """
192
+ return self.device["thermostat"]["setpoint_low"]
193
 
194
  @property
195
+ @override
196
  def hvac_mode(self) -> HVACMode:
197
  """Return HVAC operation ie. auto, cool, heat, heat_cool, or off mode."""
198
+ if (
199
+ mode := self.device.get("climate_mode")
200
+ ) is None or mode not in self.hvac_modes:
201
+ return HVACMode.HEAT
202
+ return HVACMode(mode)
 
 
 
 
 
 
203
 
204
  @property
205
+ @override
206
  def hvac_modes(self) -> list[HVACMode]:
207
  """Return a list of available HVACModes."""
208
  hvac_modes: list[HVACMode] = []
209
+ if "regulation_modes" in self._gateway_data:
210
  hvac_modes.append(HVACMode.OFF)
211
 
212
+ if self.device.get("available_schedules"):
213
  hvac_modes.append(HVACMode.AUTO)
214
 
215
  if self._api.cooling_present:
216
+ if "regulation_modes" in self._gateway_data:
217
+ if "heating" in self._gateway_data["regulation_modes"]:
218
  hvac_modes.append(HVACMode.HEAT)
219
+ if "cooling" in self._gateway_data["regulation_modes"]:
220
  hvac_modes.append(HVACMode.COOL)
221
  else:
222
  hvac_modes.append(HVACMode.HEAT_COOL)
 
226
  return hvac_modes
227
 
228
  @property
229
+ @override
230
+ def hvac_action(self) -> HVACAction:
231
  """Return the current running hvac operation if supported."""
232
  # Keep track of the previous hvac_action mode.
233
  # When no cooling available, _previous_action_mode is always heating
234
  if (
235
+ "regulation_modes" in self._gateway_data
236
+ and HVACAction.COOLING.value in self._gateway_data["regulation_modes"]
237
  ):
238
+ mode = self._gateway_data["select_regulation_mode"]
239
  if mode in (HVACAction.COOLING.value, HVACAction.HEATING.value):
240
  self._previous_action_mode = mode
241
 
242
+ if (action := self.device.get("control_state")) is not None:
243
  return HVACAction(action)
244
 
245
  return HVACAction.IDLE
246
 
247
  @property
248
+ @override
249
  def preset_mode(self) -> str | None:
250
  """Return the current preset mode."""
251
+ return self.device.get("active_preset")
252
 
253
  @plugwise_command
254
+ @override
255
  async def async_set_temperature(self, **kwargs: Any) -> None:
256
  """Set new target temperature."""
257
  data: dict[str, Any] = {}
258
  if ATTR_TEMPERATURE in kwargs:
259
+ data["setpoint"] = kwargs.get(ATTR_TEMPERATURE)
260
  if ATTR_TARGET_TEMP_HIGH in kwargs:
261
+ data["setpoint_high"] = kwargs.get(ATTR_TARGET_TEMP_HIGH)
262
  if ATTR_TARGET_TEMP_LOW in kwargs:
263
+ data["setpoint_low"] = kwargs.get(ATTR_TARGET_TEMP_LOW)
 
 
264
 
265
  if mode := kwargs.get(ATTR_HVAC_MODE):
266
  await self.async_set_hvac_mode(mode)
 
268
  await self._api.set_temperature(self._location, data)
269
 
270
  def _regulation_mode_for_hvac(self, hvac_mode: HVACMode) -> str:
271
+ """Return the API regulation value for a manual HVAC mode, or None.
272
 
273
  The function inputs are limited to the HVACModes HEAT and COOL.
274
  """
 
279
  return mode
280
 
281
  @plugwise_command
282
+ @override
283
  async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
284
  """Set the HVAC mode (off, heat, cool, heat_cool, or auto/schedule)."""
 
285
  # Early exit if no mode change
286
  if hvac_mode == self.hvac_mode:
287
  return
 
329
  await self._api.set_schedule_state(self._location, STATE_ON, desired_schedule)
330
 
331
  @plugwise_command
332
+ @override
333
  async def async_set_preset_mode(self, preset_mode: str) -> None:
334
  """Set the preset mode."""
335
  await self._api.set_preset(self._location, preset_mode)
/home/runner/work/progress/progress/clones/beta/{beta/custom_components → ha-core/homeassistant/components}/plugwise/config_flow.py RENAMED
@@ -1,10 +1,7 @@
1
  """Config flow for Plugwise integration."""
2
 
3
- # pylint: disable=home-assistant-config-flow-polling-field
4
-
5
- from copy import deepcopy
6
  import logging
7
- from typing import Any, Self
8
 
9
  from plugwise import Smile
10
  from plugwise.exceptions import (
@@ -17,15 +14,7 @@
17
  )
18
  import voluptuous as vol
19
 
20
- from homeassistant.config_entries import (
21
- SOURCE_USER,
22
- ConfigEntry,
23
- ConfigFlow,
24
- ConfigFlowResult,
25
- OptionsFlow,
26
- )
27
-
28
- # Upstream
29
  from homeassistant.const import (
30
  ATTR_CONFIGURATION_URL,
31
  CONF_BASE,
@@ -33,47 +22,30 @@
33
  CONF_NAME,
34
  CONF_PASSWORD,
35
  CONF_PORT,
36
- CONF_SCAN_INTERVAL,
37
  CONF_USERNAME,
38
  )
39
-
40
- # Upstream
41
- from homeassistant.core import HomeAssistant, callback
42
- from homeassistant.helpers import config_validation as cv
43
  from homeassistant.helpers.aiohttp_client import async_get_clientsession
44
  from homeassistant.helpers.service_info.zeroconf import ZeroconfServiceInfo
45
 
46
  from .const import (
47
  ANNA_WITH_ADAM,
48
- CONF_REFRESH_INTERVAL, # pw-beta option
49
  DEFAULT_PORT,
50
- DEFAULT_UPDATE_INTERVAL,
51
  DEFAULT_USERNAME,
52
  DOMAIN,
53
  FLOW_SMILE,
54
  FLOW_STRETCH,
55
- INIT,
56
- P1_UPDATE_INTERVAL,
57
  SMILE,
58
  SMILE_OPEN_THERM,
59
  SMILE_THERMO,
60
  STRETCH,
61
  STRETCH_USERNAME,
62
- THERMOSTAT,
63
- TITLE_PLACEHOLDERS,
64
- VERSION,
65
  ZEROCONF_MAP,
66
  )
67
 
68
- # Upstream
69
- from .coordinator import PlugwiseDataUpdateCoordinator
70
-
71
- type PlugwiseConfigEntry = ConfigEntry[PlugwiseDataUpdateCoordinator]
72
-
73
  _LOGGER = logging.getLogger(__name__)
74
 
75
- # Upstream basically the whole file (excluding the pw-beta options)
76
-
77
  SMILE_RECONF_SCHEMA = vol.Schema(
78
  {
79
  vol.Required(CONF_HOST): str,
@@ -81,31 +53,21 @@
81
  )
82
 
83
 
84
- def smile_user_schema(cf_input: ZeroconfServiceInfo | dict[str, Any] | None) -> vol.Schema:
85
  """Generate base schema for gateways."""
86
- if not cf_input: # no discovery- or user-input available
87
- return vol.Schema(
 
 
88
  {
89
  vol.Required(CONF_HOST): str,
90
- vol.Required(CONF_PASSWORD): str,
91
  vol.Required(CONF_USERNAME, default=SMILE): vol.In(
92
  {SMILE: FLOW_SMILE, STRETCH: FLOW_STRETCH}
93
  ),
94
  }
95
  )
96
 
97
- if isinstance(cf_input, ZeroconfServiceInfo):
98
- return vol.Schema({vol.Required(CONF_PASSWORD): str})
99
-
100
- return vol.Schema(
101
- {
102
- vol.Required(CONF_HOST, default=cf_input[CONF_HOST]): str,
103
- vol.Required(CONF_PASSWORD, default=cf_input[CONF_PASSWORD]): str,
104
- vol.Required(CONF_USERNAME, default=cf_input[CONF_USERNAME]): vol.In(
105
- {SMILE: FLOW_SMILE, STRETCH: FLOW_STRETCH}
106
- ),
107
- }
108
- )
109
 
110
 
111
  async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> Smile:
@@ -139,7 +101,7 @@
139
  errors[CONF_BASE] = "invalid_auth"
140
  except InvalidSetupError:
141
  errors[CONF_BASE] = "invalid_setup"
142
- except (InvalidXMLError, ResponseError):
143
  errors[CONF_BASE] = "response_error"
144
  except UnsupportedDeviceError:
145
  errors[CONF_BASE] = "unsupported"
@@ -155,33 +117,29 @@
155
  """Handle a config flow for Plugwise Smile."""
156
 
157
  VERSION = 1
158
- MINOR_VERSION = 1
159
 
160
  discovery_info: ZeroconfServiceInfo | None = None
161
- product: str = "Unknown Smile"
162
  _username: str = DEFAULT_USERNAME
163
 
 
164
  async def async_step_zeroconf(
165
  self, discovery_info: ZeroconfServiceInfo
166
  ) -> ConfigFlowResult:
167
  """Prepare configuration for a discovered Plugwise Smile."""
168
  self.discovery_info = discovery_info
169
  _properties = discovery_info.properties
170
- _version = _properties.get(VERSION, "n/a")
171
- self.product = _product = _properties.get("product", "Unknown Smile")
172
- unique_id = discovery_info.hostname.split(".")[0].split("-")[0]
173
- if DEFAULT_USERNAME not in unique_id:
174
- self._username = STRETCH_USERNAME
175
 
 
176
  if config_entry := await self.async_set_unique_id(unique_id):
177
  try:
178
  await validate_input(
179
  self.hass,
180
  {
181
  CONF_HOST: discovery_info.host,
182
- CONF_PASSWORD: config_entry.data[CONF_PASSWORD],
183
  CONF_PORT: discovery_info.port,
184
  CONF_USERNAME: config_entry.data[CONF_USERNAME],
 
185
  },
186
  )
187
  except Exception: # noqa: BLE001
@@ -194,6 +152,12 @@
194
  }
195
  )
196
 
 
 
 
 
 
 
197
  # This is an Anna, but we already have config entries.
198
  # Assuming that the user has already configured Adam, aborting discovery.
199
  if self._async_current_entries() and _product == SMILE_THERMO:
@@ -203,19 +167,19 @@
203
  # In that case, we need to cancel the Anna flow, as the Adam should
204
  # be added.
205
  if self.hass.config_entries.flow.async_has_matching_flow(self):
206
- return self.async_abort(reason="anna_with_adam")
207
 
208
- _name = f"{ZEROCONF_MAP.get(_product, _product)} v{_version}"
209
  self.context.update(
210
  {
211
- TITLE_PLACEHOLDERS: {CONF_NAME: _name},
212
  ATTR_CONFIGURATION_URL: (
213
  f"http://{discovery_info.host}:{discovery_info.port}"
214
- )
215
  }
216
  )
217
  return await self.async_step_user()
218
 
 
219
  def is_matching(self, other_flow: Self) -> bool:
220
  """Return True if other_flow is matching this flow."""
221
  # This is an Anna, and there is already an Adam flow in progress
@@ -228,7 +192,7 @@
228
 
229
  return False
230
 
231
-
232
  async def async_step_user(
233
  self, user_input: dict[str, Any] | None = None
234
  ) -> ConfigFlowResult:
@@ -245,19 +209,18 @@
245
  api, errors = await verify_connection(self.hass, user_input)
246
  if api:
247
  await self.async_set_unique_id(
248
- api.smile.hostname or api.gateway_id, raise_on_progress=False
 
249
  )
250
  self._abort_if_unique_id_configured()
251
  return self.async_create_entry(title=api.smile.name, data=user_input)
252
 
253
- configure_input = self.discovery_info or user_input
254
  return self.async_show_form(
255
  step_id=SOURCE_USER,
256
- data_schema=smile_user_schema(configure_input),
257
  errors=errors,
258
  )
259
 
260
-
261
  async def async_step_reconfigure(
262
  self, user_input: dict[str, Any] | None = None
263
  ) -> ConfigFlowResult:
@@ -267,7 +230,7 @@
267
  reconfigure_entry = self._get_reconfigure_entry()
268
 
269
  if user_input:
270
- # Redefine ingest existing username and password
271
  full_input = {
272
  CONF_HOST: user_input.get(CONF_HOST),
273
  CONF_PORT: reconfigure_entry.data.get(CONF_PORT),
@@ -278,7 +241,8 @@
278
  api, errors = await verify_connection(self.hass, full_input)
279
  if api:
280
  await self.async_set_unique_id(
281
- api.smile.hostname or api.gateway_id, raise_on_progress=False
 
282
  )
283
  self._abort_if_unique_id_mismatch(reason="not_the_same_smile")
284
  return self.async_update_reload_and_abort(
@@ -295,66 +259,3 @@
295
  description_placeholders={"title": reconfigure_entry.title},
296
  errors=errors,
297
  )
298
-
299
-
300
- @staticmethod
301
- @callback
302
- def async_get_options_flow(
303
- config_entry: PlugwiseConfigEntry,
304
- ) -> PlugwiseOptionsFlowHandler: # pw-beta options
305
- """Get the options flow for this handler."""
306
- return PlugwiseOptionsFlowHandler(config_entry)
307
-
308
-
309
- # pw-beta - change the scan-interval via CONFIGURE
310
- # pw-beta - change the frontend refresh interval via CONFIGURE
311
- class PlugwiseOptionsFlowHandler(OptionsFlow): # pw-beta options
312
- """Plugwise option flow."""
313
-
314
- def __init__(self, config_entry: ConfigEntry) -> None:
315
- """Initialize options flow."""
316
- self.options = deepcopy(dict(config_entry.options))
317
-
318
- def _create_options_schema(self, coordinator: PlugwiseDataUpdateCoordinator) -> vol.Schema:
319
- interval = DEFAULT_UPDATE_INTERVAL
320
- if coordinator.api.smile.type == "power":
321
- interval = P1_UPDATE_INTERVAL
322
- schema = {
323
- vol.Optional(
324
- CONF_SCAN_INTERVAL,
325
- default=self.options.get(CONF_SCAN_INTERVAL, interval.seconds),
326
- ): vol.All(cv.positive_int, vol.Clamp(min=10)),
327
- } # pw-beta
328
-
329
- if coordinator.api.smile.type == THERMOSTAT:
330
- schema.update({
331
- vol.Optional(
332
- CONF_REFRESH_INTERVAL,
333
- default=self.options.get(CONF_REFRESH_INTERVAL, 1.5),
334
- ): vol.All(vol.Coerce(float), vol.Range(min=1.5, max=10.0)),
335
- }) # pw-beta
336
-
337
- return vol.Schema(schema)
338
-
339
- async def async_step_none(
340
- self, user_input: dict[str, Any] | None = None
341
- ) -> ConfigFlowResult: # pragma: no cover
342
- """No options available."""
343
- if user_input is not None:
344
- # Apparently not possible to abort an options flow at the moment
345
- return self.async_create_entry(title="", data=self.options)
346
- return self.async_show_form(step_id="none")
347
-
348
- async def async_step_init(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult:
349
- """Manage the Plugwise options."""
350
- if not self.config_entry.data.get(CONF_HOST):
351
- return await self.async_step_none(user_input) # pragma: no cover
352
-
353
- if user_input is not None:
354
- return self.async_create_entry(title="", data=user_input)
355
-
356
- coordinator = self.config_entry.runtime_data
357
- return self.async_show_form(
358
- step_id=INIT,
359
- data_schema=self._create_options_schema(coordinator)
360
- )
 
1
  """Config flow for Plugwise integration."""
2
 
 
 
 
3
  import logging
4
+ from typing import Any, Self, override
5
 
6
  from plugwise import Smile
7
  from plugwise.exceptions import (
 
14
  )
15
  import voluptuous as vol
16
 
17
+ from homeassistant.config_entries import SOURCE_USER, ConfigFlow, ConfigFlowResult
 
 
 
 
 
 
 
 
18
  from homeassistant.const import (
19
  ATTR_CONFIGURATION_URL,
20
  CONF_BASE,
 
22
  CONF_NAME,
23
  CONF_PASSWORD,
24
  CONF_PORT,
 
25
  CONF_USERNAME,
26
  )
27
+ from homeassistant.core import HomeAssistant
 
 
 
28
  from homeassistant.helpers.aiohttp_client import async_get_clientsession
29
  from homeassistant.helpers.service_info.zeroconf import ZeroconfServiceInfo
30
 
31
  from .const import (
32
  ANNA_WITH_ADAM,
 
33
  DEFAULT_PORT,
 
34
  DEFAULT_USERNAME,
35
  DOMAIN,
36
  FLOW_SMILE,
37
  FLOW_STRETCH,
 
 
38
  SMILE,
39
  SMILE_OPEN_THERM,
40
  SMILE_THERMO,
41
  STRETCH,
42
  STRETCH_USERNAME,
43
+ UNKNOWN_SMILE,
 
 
44
  ZEROCONF_MAP,
45
  )
46
 
 
 
 
 
 
47
  _LOGGER = logging.getLogger(__name__)
48
 
 
 
49
  SMILE_RECONF_SCHEMA = vol.Schema(
50
  {
51
  vol.Required(CONF_HOST): str,
 
53
  )
54
 
55
 
56
+ def smile_user_schema(discovery_info: ZeroconfServiceInfo | None) -> vol.Schema:
57
  """Generate base schema for gateways."""
58
+ schema = vol.Schema({vol.Required(CONF_PASSWORD): str})
59
+
60
+ if not discovery_info:
61
+ schema = schema.extend(
62
  {
63
  vol.Required(CONF_HOST): str,
 
64
  vol.Required(CONF_USERNAME, default=SMILE): vol.In(
65
  {SMILE: FLOW_SMILE, STRETCH: FLOW_STRETCH}
66
  ),
67
  }
68
  )
69
 
70
+ return schema
 
 
 
 
 
 
 
 
 
 
 
71
 
72
 
73
  async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> Smile:
 
101
  errors[CONF_BASE] = "invalid_auth"
102
  except InvalidSetupError:
103
  errors[CONF_BASE] = "invalid_setup"
104
+ except InvalidXMLError, ResponseError:
105
  errors[CONF_BASE] = "response_error"
106
  except UnsupportedDeviceError:
107
  errors[CONF_BASE] = "unsupported"
 
117
  """Handle a config flow for Plugwise Smile."""
118
 
119
  VERSION = 1
 
120
 
121
  discovery_info: ZeroconfServiceInfo | None = None
122
+ product: str = UNKNOWN_SMILE
123
  _username: str = DEFAULT_USERNAME
124
 
125
+ @override
126
  async def async_step_zeroconf(
127
  self, discovery_info: ZeroconfServiceInfo
128
  ) -> ConfigFlowResult:
129
  """Prepare configuration for a discovered Plugwise Smile."""
130
  self.discovery_info = discovery_info
131
  _properties = discovery_info.properties
 
 
 
 
 
132
 
133
+ unique_id = discovery_info.hostname.split(".")[0].split("-")[0]
134
  if config_entry := await self.async_set_unique_id(unique_id):
135
  try:
136
  await validate_input(
137
  self.hass,
138
  {
139
  CONF_HOST: discovery_info.host,
 
140
  CONF_PORT: discovery_info.port,
141
  CONF_USERNAME: config_entry.data[CONF_USERNAME],
142
+ CONF_PASSWORD: config_entry.data[CONF_PASSWORD],
143
  },
144
  )
145
  except Exception: # noqa: BLE001
 
152
  }
153
  )
154
 
155
+ if DEFAULT_USERNAME not in unique_id:
156
+ self._username = STRETCH_USERNAME
157
+ self.product = _product = _properties.get("product", UNKNOWN_SMILE)
158
+ _version = _properties.get("version", "n/a")
159
+ _name = f"{ZEROCONF_MAP.get(_product, _product)} v{_version}"
160
+
161
  # This is an Anna, but we already have config entries.
162
  # Assuming that the user has already configured Adam, aborting discovery.
163
  if self._async_current_entries() and _product == SMILE_THERMO:
 
167
  # In that case, we need to cancel the Anna flow, as the Adam should
168
  # be added.
169
  if self.hass.config_entries.flow.async_has_matching_flow(self):
170
+ return self.async_abort(reason=ANNA_WITH_ADAM)
171
 
 
172
  self.context.update(
173
  {
174
+ "title_placeholders": {CONF_NAME: _name},
175
  ATTR_CONFIGURATION_URL: (
176
  f"http://{discovery_info.host}:{discovery_info.port}"
177
+ ),
178
  }
179
  )
180
  return await self.async_step_user()
181
 
182
+ @override
183
  def is_matching(self, other_flow: Self) -> bool:
184
  """Return True if other_flow is matching this flow."""
185
  # This is an Anna, and there is already an Adam flow in progress
 
192
 
193
  return False
194
 
195
+ @override
196
  async def async_step_user(
197
  self, user_input: dict[str, Any] | None = None
198
  ) -> ConfigFlowResult:
 
209
  api, errors = await verify_connection(self.hass, user_input)
210
  if api:
211
  await self.async_set_unique_id(
212
+ api.smile.hostname or api.gateway_id,
213
+ raise_on_progress=False,
214
  )
215
  self._abort_if_unique_id_configured()
216
  return self.async_create_entry(title=api.smile.name, data=user_input)
217
 
 
218
  return self.async_show_form(
219
  step_id=SOURCE_USER,
220
+ data_schema=smile_user_schema(self.discovery_info),
221
  errors=errors,
222
  )
223
 
 
224
  async def async_step_reconfigure(
225
  self, user_input: dict[str, Any] | None = None
226
  ) -> ConfigFlowResult:
 
230
  reconfigure_entry = self._get_reconfigure_entry()
231
 
232
  if user_input:
233
+ # Keep current username and password
234
  full_input = {
235
  CONF_HOST: user_input.get(CONF_HOST),
236
  CONF_PORT: reconfigure_entry.data.get(CONF_PORT),
 
241
  api, errors = await verify_connection(self.hass, full_input)
242
  if api:
243
  await self.async_set_unique_id(
244
+ api.smile.hostname or api.gateway_id,
245
+ raise_on_progress=False,
246
  )
247
  self._abort_if_unique_id_mismatch(reason="not_the_same_smile")
248
  return self.async_update_reload_and_abort(
 
259
  description_placeholders={"title": reconfigure_entry.title},
260
  errors=errors,
261
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
/home/runner/work/progress/progress/clones/beta/{beta/custom_components → ha-core/homeassistant/components}/plugwise/const.py RENAMED
@@ -6,157 +6,28 @@
6
 
7
  from homeassistant.const import Platform
8
 
9
- # Upstream basically the whole file excluding pw-beta options
10
-
11
  DOMAIN: Final = "plugwise"
12
 
13
  LOGGER = logging.getLogger(__package__)
14
 
 
15
  API: Final = "api"
16
- COORDINATOR: Final = "coordinator"
17
- CONFIG_ENTRY: Final = "config_entry" # pw-beta service
18
- CONF_HOMEKIT_EMULATION: Final = "homekit_emulation" # pw-beta options
19
- CONF_REFRESH_INTERVAL: Final = "refresh_interval" # pw-beta options
20
- CONF_MANUAL_PATH: Final = "Enter Manually"
21
  GATEWAY: Final = "gateway"
22
  LOCATION: Final = "location"
23
- MAC_ADDRESS: Final = "mac_address"
24
  REBOOT: Final = "reboot"
25
  SMILE: Final = "smile"
26
- STRETCH: Final = "stretch"
27
- STRETCH_USERNAME: Final = "stretch"
28
- SWITCH_GROUPS: Final[tuple[str, str]] = ("report", "switching")
29
- UNIQUE_IDS: Final = "unique_ids"
30
- ZIGBEE_MAC_ADDRESS: Final = "zigbee_mac_address"
31
-
32
- # Binary Sensor constants
33
- BINARY_SENSORS: Final = "binary_sensors"
34
- BATTERY_STATE: Final = "low_battery"
35
- COMPRESSOR_STATE: Final = "compressor_state"
36
- COOLING_ENABLED: Final = "cooling_enabled"
37
- COOLING_STATE: Final = "cooling_state"
38
- DHW_STATE: Final = "dhw_state"
39
- FLAME_STATE: Final = "flame_state"
40
- HEATING_STATE: Final = "heating_state"
41
- PLUGWISE_NOTIFICATION: Final = "plugwise_notification"
42
- SECONDARY_BOILER_STATE: Final = "secondary_boiler_state"
43
-
44
- # Climate constants
45
- ACTIVE_PRESET: Final = "active_preset"
46
- CLIMATE_MODE: Final = "climate_mode"
47
- CONTROL_STATE: Final = "control_state"
48
- COOLING_PRESENT: Final ="cooling_present"
49
- DEV_CLASS: Final = "dev_class"
50
- NONE : Final = "None"
51
- TARGET_TEMP: Final = "setpoint"
52
- TARGET_TEMP_HIGH: Final = "setpoint_high"
53
- TARGET_TEMP_LOW: Final = "setpoint_low"
54
- THERMOSTAT: Final = "thermostat"
55
-
56
- # Config_flow constants
57
- ANNA_WITH_ADAM: Final = "anna_with_adam"
58
- CONTEXT: Final = "context"
59
- FLOW_ID: Final = "flow_id"
60
- FLOW_NET: Final = "Network: Smile/Stretch"
61
- FLOW_SMILE: Final = "Smile (Adam/Anna/P1)"
62
- FLOW_STRETCH: Final = "Stretch (Stretch)"
63
- FLOW_TYPE: Final = "flow_type"
64
- INIT: Final = "init"
65
- PRODUCT: Final = "product"
66
  SMILE_OPEN_THERM: Final = "smile_open_therm"
67
  SMILE_THERMO: Final = "smile_thermo"
68
- TITLE_PLACEHOLDERS: Final = "title_placeholders"
69
- VERSION: Final = "version"
70
-
71
- # Entity constants
72
- AVAILABLE: Final = "available"
73
- FIRMWARE: Final = "firmware"
74
- HARDWARE: Final = "hardware"
75
- MODEL: Final = "model"
76
- MODEL_ID: Final = "model_id"
77
- VENDOR: Final = "vendor"
78
-
79
- # Number constants
80
- MAX_BOILER_TEMP: Final = "maximum_boiler_temperature"
81
- MAX_DHW_TEMP: Final = "max_dhw_temperature"
82
- LOWER_BOUND: Final = "lower_bound"
83
- RESOLUTION: Final = "resolution"
84
- TEMPERATURE_OFFSET: Final = "temperature_offset"
85
- UPPER_BOUND: Final = "upper_bound"
86
-
87
- # Sensor constants
88
- DHW_TEMP: Final = "dhw_temperature"
89
- DHW_SETPOINT: Final = "domestic_hot_water_setpoint"
90
- EL_CONSUMED: Final = "electricity_consumed"
91
- EL_CONS_INTERVAL: Final = "electricity_consumed_interval"
92
- EL_CONS_OP_CUMULATIVE: Final = "electricity_consumed_off_peak_cumulative"
93
- EL_CONS_OP_INTERVAL: Final = "electricity_consumed_off_peak_interval"
94
- EL_CONS_OP_POINT: Final = "electricity_consumed_off_peak_point"
95
- EL_CONS_P_CUMULATIVE: Final = "electricity_consumed_peak_cumulative"
96
- EL_CONS_P_INTERVAL: Final = "electricity_consumed_peak_interval"
97
- EL_CONS_P_POINT: Final = "electricity_consumed_peak_point"
98
- EL_CONS_POINT: Final = "electricity_consumed_point"
99
- EL_PH1_CONSUMED: Final = "electricity_phase_one_consumed"
100
- EL_PH2_CONSUMED: Final = "electricity_phase_two_consumed"
101
- EL_PH3_CONSUMED: Final = "electricity_phase_three_consumed"
102
- EL_PH1_PRODUCED: Final = "electricity_phase_one_produced"
103
- EL_PH2_PRODUCED: Final = "electricity_phase_two_produced"
104
- EL_PH3_PRODUCED: Final = "electricity_phase_three_produced"
105
- EL_PRODUCED: Final = "electricity_produced"
106
- EL_PROD_INTERVAL: Final = "electricity_produced_interval"
107
- EL_PROD_OP_CUMULATIVE: Final = "electricity_produced_off_peak_cumulative"
108
- EL_PROD_OP_INTERVAL: Final = "electricity_produced_off_peak_interval"
109
- EL_PROD_OP_POINT: Final = "electricity_produced_off_peak_point"
110
- EL_PROD_P_CUMULATIVE: Final = "electricity_produced_peak_cumulative"
111
- EL_PROD_P_INTERVAL: Final = "electricity_produced_peak_interval"
112
- EL_PROD_P_POINT: Final = "electricity_produced_peak_point"
113
- EL_PROD_POINT: Final = "electricity_produced_point"
114
- GAS_CONS_CUMULATIVE: Final = "gas_consumed_cumulative"
115
- GAS_CONS_INTERVAL: Final = "gas_consumed_interval"
116
- INTENDED_BOILER_TEMP: Final = "intended_boiler_temperature"
117
- MOD_LEVEL: Final = "modulation_level"
118
- NET_EL_POINT: Final = "net_electricity_point"
119
- NET_EL_CUMULATIVE: Final = "net_electricity_cumulative"
120
- OUTDOOR_AIR_TEMP: Final = "outdoor_air_temperature"
121
- OUTDOOR_TEMP: Final = "outdoor_temperature"
122
- RETURN_TEMP: Final = "return_temperature"
123
- SENSORS: Final = "sensors"
124
- TEMP_DIFF: Final = "temperature_difference"
125
- VALVE_POS: Final = "valve_position"
126
- VOLTAGE_PH1: Final = "voltage_phase_one"
127
- VOLTAGE_PH2: Final = "voltage_phase_two"
128
- VOLTAGE_PH3: Final = "voltage_phase_three"
129
- WATER_TEMP: Final = "water_temperature"
130
- WATER_PRESSURE: Final = "water_pressure"
131
-
132
- # Select constants
133
- AVAILABLE_SCHEDULES: Final = "available_schedules"
134
- DHW_MODES: Final = "dhw_modes"
135
- GATEWAY_MODES: Final = "gateway_modes"
136
- REGULATION_MODES: Final = "regulation_modes"
137
- ZONE_PROFILES: Final = "zone_profiles"
138
- SELECT_DHW_MODE: Final = "select_dhw_mode"
139
- SELECT_GATEWAY_MODE: Final = "select_gateway_mode"
140
- SELECT_REGULATION_MODE: Final = "select_regulation_mode"
141
- SELECT_SCHEDULE: Final = "select_schedule"
142
- SELECT_ZONE_PROFILE: Final = "select_zone_profile"
143
-
144
- # Switch constants
145
- DHW_CM_SWITCH: Final = "dhw_cm_switch"
146
- LOCK: Final = "lock"
147
- MEMBERS: Final ="members"
148
- RELAY: Final = "relay"
149
- COOLING_ENA_SWITCH: Final ="cooling_ena_switch"
150
- SWITCHES: Final = "switches"
151
-
152
- # Default directives
153
- DEFAULT_PORT: Final[int] = 80
154
- DEFAULT_TIMEOUT: Final[int] = 30
155
- DEFAULT_UPDATE_INTERVAL: Final = timedelta(seconds=60)
156
- DEFAULT_USERNAME: Final = "smile"
157
- P1_UPDATE_INTERVAL: Final = timedelta(seconds=10)
158
 
159
- # --- Const for Plugwise Smile and Stretch
160
  PLATFORMS: Final[list[str]] = [
161
  Platform.BINARY_SENSOR,
162
  Platform.BUTTON,
@@ -166,18 +37,6 @@
166
  Platform.SENSOR,
167
  Platform.SWITCH,
168
  ]
169
- SERVICE_DELETE: Final = "delete_notification"
170
- SEVERITIES: Final[list[str]] = ["other", "info", "message", "warning", "error"]
171
-
172
- # Climate const:
173
- MASTER_THERMOSTATS: Final[list[str]] = [
174
- "thermostat",
175
- "zone_thermometer",
176
- "zone_thermostat",
177
- "thermostatic_radiator_valve",
178
- ]
179
-
180
- # Config_flow const:
181
  ZEROCONF_MAP: Final[dict[str, str]] = {
182
  "smile": "Smile P1",
183
  "smile_thermo": "Smile Anna",
@@ -196,7 +55,7 @@
196
  "select_gateway_mode",
197
  "select_regulation_mode",
198
  "select_schedule",
199
- "select_zone_profile"
200
  ]
201
  type SelectOptionsType = Literal[
202
  "available_schedules",
@@ -205,3 +64,25 @@
205
  "regulation_modes",
206
  "zone_profiles",
207
  ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
 
7
  from homeassistant.const import Platform
8
 
 
 
9
  DOMAIN: Final = "plugwise"
10
 
11
  LOGGER = logging.getLogger(__package__)
12
 
13
+ ANNA_WITH_ADAM: Final = "anna_with_adam"
14
  API: Final = "api"
15
+ AVAILABLE: Final = "available"
16
+ DEV_CLASS: Final = "dev_class"
17
+ FLOW_SMILE: Final = "smile (Adam/Anna/P1)"
18
+ FLOW_STRETCH: Final = "stretch (Stretch)"
19
+ FLOW_TYPE: Final = "flow_type"
20
  GATEWAY: Final = "gateway"
21
  LOCATION: Final = "location"
22
+ PW_TYPE: Final = "plugwise_type"
23
  REBOOT: Final = "reboot"
24
  SMILE: Final = "smile"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  SMILE_OPEN_THERM: Final = "smile_open_therm"
26
  SMILE_THERMO: Final = "smile_thermo"
27
+ STRETCH: Final = "stretch"
28
+ STRETCH_USERNAME: Final = "stretch"
29
+ UNKNOWN_SMILE: Final = "Unknown Smile"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
 
 
31
  PLATFORMS: Final[list[str]] = [
32
  Platform.BINARY_SENSOR,
33
  Platform.BUTTON,
 
37
  Platform.SENSOR,
38
  Platform.SWITCH,
39
  ]
 
 
 
 
 
 
 
 
 
 
 
 
40
  ZEROCONF_MAP: Final[dict[str, str]] = {
41
  "smile": "Smile P1",
42
  "smile_thermo": "Smile Anna",
 
55
  "select_gateway_mode",
56
  "select_regulation_mode",
57
  "select_schedule",
58
+ "select_zone_profile",
59
  ]
60
  type SelectOptionsType = Literal[
61
  "available_schedules",
 
64
  "regulation_modes",
65
  "zone_profiles",
66
  ]
67
+
68
+ # Default directives
69
+ DEFAULT_MAX_TEMP: Final = 30
70
+ DEFAULT_MIN_TEMP: Final = 4
71
+ DEFAULT_PORT: Final = 80
72
+ DEFAULT_UPDATE_INTERVAL = timedelta(seconds=60)
73
+ DEFAULT_USERNAME: Final = "smile"
74
+ P1_UPDATE_INTERVAL = timedelta(seconds=10)
75
+
76
+ MASTER_THERMOSTATS: Final[list[str]] = [
77
+ "thermostat",
78
+ "thermostatic_radiator_valve",
79
+ "zone_thermometer",
80
+ "zone_thermostat",
81
+ ]
82
+
83
+ # Select constants
84
+ SELECT_DHW_MODE: Final = "select_dhw_mode"
85
+ SELECT_GATEWAY_MODE: Final = "select_gateway_mode"
86
+ SELECT_REGULATION_MODE: Final = "select_regulation_mode"
87
+ SELECT_SCHEDULE: Final = "select_schedule"
88
+ SELECT_ZONE_PROFILE: Final = "select_zone_profile"
/home/runner/work/progress/progress/clones/beta/{beta/custom_components → ha-core/homeassistant/components}/plugwise/coordinator.py RENAMED
@@ -1,7 +1,8 @@
1
  """DataUpdateCoordinator for Plugwise."""
2
 
3
- from datetime import timedelta
4
 
 
5
  from plugwise import GwEntityData, Smile
6
  from plugwise.exceptions import (
7
  ConnectionFailedError,
@@ -14,29 +15,21 @@
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
27
  from homeassistant.helpers.aiohttp_client import async_get_clientsession
28
  from homeassistant.helpers.debounce import Debouncer
29
  from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
30
- from packaging.version import Version
31
 
32
  from .const import (
 
33
  DEFAULT_UPDATE_INTERVAL,
34
- DEV_CLASS,
35
  DOMAIN,
36
- FIRMWARE,
37
  LOGGER,
38
  P1_UPDATE_INTERVAL,
39
- SWITCH_GROUPS,
40
  )
41
 
42
  type PlugwiseConfigEntry = ConfigEntry[PlugwiseDataUpdateCoordinator]
@@ -47,36 +40,29 @@
47
 
48
  config_entry: PlugwiseConfigEntry
49
 
50
- def __init__(
51
- self,
52
- hass: HomeAssistant,
53
- cooldown: float,
54
- config_entry: PlugwiseConfigEntry,
55
- ) -> None: # pw-beta cooldown
56
  """Initialize the coordinator."""
57
  super().__init__(
58
  hass,
59
  LOGGER,
60
  config_entry=config_entry,
61
  name=DOMAIN,
62
- # Core directly updates from const's DEFAULT_SCAN_INTERVAL
63
- # Upstream check correct progress for adjusting
64
  update_interval=DEFAULT_UPDATE_INTERVAL,
65
  # Don't refresh immediately, give the device time to process
66
  # the change in state before we query it.
67
  request_refresh_debouncer=Debouncer(
68
  hass,
69
  LOGGER,
70
- cooldown=cooldown,
71
  immediate=False,
72
  ),
73
  )
74
 
75
  self.api = Smile(
76
  host=self.config_entry.data[CONF_HOST],
 
77
  password=self.config_entry.data[CONF_PASSWORD],
78
- port=self.config_entry.data[CONF_PORT],
79
- username=self.config_entry.data[CONF_USERNAME],
80
  websession=async_get_clientsession(hass, verify_ssl=False),
81
  )
82
  self._connected: bool = False
@@ -92,16 +78,10 @@
92
  """
93
  version = await self.api.connect()
94
  self._connected = isinstance(version, Version)
95
- if self._connected:
96
- if self.api.smile.type == "power":
97
- self.update_interval = P1_UPDATE_INTERVAL
98
- if (custom_time := self.config_entry.options.get(CONF_SCAN_INTERVAL)) is not None:
99
- self.update_interval = timedelta(
100
- seconds=int(custom_time)
101
- ) # pragma: no cover # pw-beta options
102
-
103
- LOGGER.debug("DUC update interval: %s", self.update_interval) # pw-beta options
104
 
 
105
  async def _async_setup(self) -> None:
106
  """Initialize the update_data process."""
107
  device_reg = dr.async_get(self.hass)
@@ -115,6 +95,7 @@
115
  if identifier[0] == DOMAIN
116
  }
117
 
 
118
  async def _async_update_data(self) -> dict[str, GwEntityData]:
119
  """Fetch data from Plugwise."""
120
  try:
@@ -152,27 +133,25 @@
152
  translation_key="unsupported_firmware",
153
  ) from err
154
 
155
- LOGGER.debug("%s data: %s", self.api.smile.name, data)
156
  self._add_remove_devices(data)
157
  self._update_device_firmware(data)
158
  return data
159
 
160
  def _add_remove_devices(self, data: dict[str, GwEntityData]) -> None:
161
  """Add new Plugwise devices, remove non-existing devices."""
162
- # Block switch-groups, use HA group helper instead to create switch-groups
163
- for device_id, device in data.copy().items():
164
- if device.get(DEV_CLASS) in SWITCH_GROUPS:
165
- data.pop(device_id)
166
-
167
- # Collect new or removed devices,
168
- # 'new_devices' contains all devices present in 'data' at init ('self._current_devices' is empty)
169
- # this is required for the initialization of the available platform entities.
170
  set_of_data = set(data)
 
 
 
 
 
171
  self.new_devices = set_of_data - self._current_devices
172
  for device_id in self.new_devices:
173
- self._firmware_list.setdefault(device_id, data[device_id].get(FIRMWARE))
174
 
175
- current_devices = self._stored_devices if not self._current_devices else self._current_devices
 
 
176
  self._current_devices = set_of_data
177
  if removed_devices := (current_devices - set_of_data): # device(s) to remove
178
  self._remove_devices(removed_devices)
@@ -181,7 +160,9 @@
181
  """Clean registries when removed devices found."""
182
  device_reg = dr.async_get(self.hass)
183
  for device_id in removed_devices:
184
- if (device_entry := device_reg.async_get_device({(DOMAIN, device_id)})) is not None:
 
 
185
  device_reg.async_update_device(
186
  device_entry.id, remove_config_entry_id=self.config_entry.entry_id
187
  )
@@ -194,15 +175,14 @@
194
 
195
  self._firmware_list.pop(device_id, None)
196
 
197
-
198
  def _update_device_firmware(self, data: dict[str, GwEntityData]) -> None:
199
  """Detect firmware changes and update the device registry."""
200
  for device_id, device in data.items():
201
  # Only update firmware when the key is present and not None, to avoid
202
  # wiping stored firmware on partial or transient updates.
203
- if FIRMWARE not in device:
204
  continue
205
- new_firmware = device.get(FIRMWARE)
206
  if new_firmware is None:
207
  continue
208
  if (
@@ -216,7 +196,9 @@
216
  def _update_firmware_in_dr(self, device_id: str, firmware: str | None) -> bool:
217
  """Update device sw_version in device_registry."""
218
  device_reg = dr.async_get(self.hass)
219
- if (device_entry := device_reg.async_get_device({(DOMAIN, device_id)})) is not None:
 
 
220
  device_reg.async_update_device(device_entry.id, sw_version=firmware)
221
  LOGGER.debug(
222
  "Firmware in device_registry updated for %s %s %s",
 
1
  """DataUpdateCoordinator for Plugwise."""
2
 
3
+ from typing import override
4
 
5
+ from packaging.version import Version
6
  from plugwise import GwEntityData, Smile
7
  from plugwise.exceptions import (
8
  ConnectionFailedError,
 
15
  )
16
 
17
  from homeassistant.config_entries import ConfigEntry
18
+ from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_USERNAME
 
 
 
 
 
 
19
  from homeassistant.core import HomeAssistant
20
  from homeassistant.exceptions import ConfigEntryError
21
  from homeassistant.helpers import device_registry as dr
22
  from homeassistant.helpers.aiohttp_client import async_get_clientsession
23
  from homeassistant.helpers.debounce import Debouncer
24
  from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
 
25
 
26
  from .const import (
27
+ DEFAULT_PORT,
28
  DEFAULT_UPDATE_INTERVAL,
29
+ DEFAULT_USERNAME,
30
  DOMAIN,
 
31
  LOGGER,
32
  P1_UPDATE_INTERVAL,
 
33
  )
34
 
35
  type PlugwiseConfigEntry = ConfigEntry[PlugwiseDataUpdateCoordinator]
 
40
 
41
  config_entry: PlugwiseConfigEntry
42
 
43
+ def __init__(self, hass: HomeAssistant, config_entry: PlugwiseConfigEntry) -> None:
 
 
 
 
 
44
  """Initialize the coordinator."""
45
  super().__init__(
46
  hass,
47
  LOGGER,
48
  config_entry=config_entry,
49
  name=DOMAIN,
 
 
50
  update_interval=DEFAULT_UPDATE_INTERVAL,
51
  # Don't refresh immediately, give the device time to process
52
  # the change in state before we query it.
53
  request_refresh_debouncer=Debouncer(
54
  hass,
55
  LOGGER,
56
+ cooldown=1.5,
57
  immediate=False,
58
  ),
59
  )
60
 
61
  self.api = Smile(
62
  host=self.config_entry.data[CONF_HOST],
63
+ username=self.config_entry.data.get(CONF_USERNAME, DEFAULT_USERNAME),
64
  password=self.config_entry.data[CONF_PASSWORD],
65
+ port=self.config_entry.data.get(CONF_PORT, DEFAULT_PORT),
 
66
  websession=async_get_clientsession(hass, verify_ssl=False),
67
  )
68
  self._connected: bool = False
 
78
  """
79
  version = await self.api.connect()
80
  self._connected = isinstance(version, Version)
81
+ if self._connected and self.api.smile.type == "power":
82
+ self.update_interval = P1_UPDATE_INTERVAL
 
 
 
 
 
 
 
83
 
84
+ @override
85
  async def _async_setup(self) -> None:
86
  """Initialize the update_data process."""
87
  device_reg = dr.async_get(self.hass)
 
95
  if identifier[0] == DOMAIN
96
  }
97
 
98
+ @override
99
  async def _async_update_data(self) -> dict[str, GwEntityData]:
100
  """Fetch data from Plugwise."""
101
  try:
 
133
  translation_key="unsupported_firmware",
134
  ) from err
135
 
 
136
  self._add_remove_devices(data)
137
  self._update_device_firmware(data)
138
  return data
139
 
140
  def _add_remove_devices(self, data: dict[str, GwEntityData]) -> None:
141
  """Add new Plugwise devices, remove non-existing devices."""
 
 
 
 
 
 
 
 
142
  set_of_data = set(data)
143
+ # Check for new or removed devices,
144
+ # 'new_devices' contains all devices present in 'data'
145
+ # at init ('self._current_devices' is empty) this is
146
+ # required for the proper initialization of all the
147
+ # present platform entities.
148
  self.new_devices = set_of_data - self._current_devices
149
  for device_id in self.new_devices:
150
+ self._firmware_list.setdefault(device_id, data[device_id].get("firmware"))
151
 
152
+ current_devices = (
153
+ self._stored_devices if not self._current_devices else self._current_devices
154
+ )
155
  self._current_devices = set_of_data
156
  if removed_devices := (current_devices - set_of_data): # device(s) to remove
157
  self._remove_devices(removed_devices)
 
160
  """Clean registries when removed devices found."""
161
  device_reg = dr.async_get(self.hass)
162
  for device_id in removed_devices:
163
+ if (
164
+ device_entry := device_reg.async_get_device({(DOMAIN, device_id)})
165
+ ) is not None:
166
  device_reg.async_update_device(
167
  device_entry.id, remove_config_entry_id=self.config_entry.entry_id
168
  )
 
175
 
176
  self._firmware_list.pop(device_id, None)
177
 
 
178
  def _update_device_firmware(self, data: dict[str, GwEntityData]) -> None:
179
  """Detect firmware changes and update the device registry."""
180
  for device_id, device in data.items():
181
  # Only update firmware when the key is present and not None, to avoid
182
  # wiping stored firmware on partial or transient updates.
183
+ if "firmware" not in device:
184
  continue
185
+ new_firmware = device.get("firmware")
186
  if new_firmware is None:
187
  continue
188
  if (
 
196
  def _update_firmware_in_dr(self, device_id: str, firmware: str | None) -> bool:
197
  """Update device sw_version in device_registry."""
198
  device_reg = dr.async_get(self.hass)
199
+ if (
200
+ device_entry := device_reg.async_get_device({(DOMAIN, device_id)})
201
+ ) is not None:
202
  device_reg.async_update_device(device_entry.id, sw_version=firmware)
203
  LOGGER.debug(
204
  "Firmware in device_registry updated for %s %s %s",
/home/runner/work/progress/progress/clones/beta/{beta/custom_components → ha-core/homeassistant/components}/plugwise/entity.py RENAMED
@@ -1,6 +1,8 @@
1
  """Generic Plugwise Entity Class."""
2
 
3
- from plugwise.constants import GwEntityData
 
 
4
 
5
  from homeassistant.const import ATTR_NAME, ATTR_VIA_DEVICE, CONF_HOST
6
  from homeassistant.helpers.device_registry import (
@@ -10,19 +12,7 @@
10
  )
11
  from homeassistant.helpers.update_coordinator import CoordinatorEntity
12
 
13
- from .const import (
14
- AVAILABLE,
15
- DOMAIN,
16
- FIRMWARE,
17
- HARDWARE,
18
- MAC_ADDRESS,
19
- MODEL,
20
- MODEL_ID,
21
- VENDOR,
22
- ZIGBEE_MAC_ADDRESS,
23
- )
24
-
25
- # Upstream consts
26
  from .coordinator import PlugwiseDataUpdateCoordinator
27
 
28
 
@@ -53,9 +43,9 @@
53
 
54
  # Build connections set
55
  connections = set()
56
- if mac := self.device.get(MAC_ADDRESS):
57
  connections.add((CONNECTION_NETWORK_MAC, mac))
58
- if zigbee_mac := self.device.get(ZIGBEE_MAC_ADDRESS):
59
  connections.add((CONNECTION_ZIGBEE, zigbee_mac))
60
 
61
  # Set base device info
@@ -63,12 +53,12 @@
63
  configuration_url=configuration_url,
64
  identifiers={(DOMAIN, device_id)},
65
  connections=connections,
66
- manufacturer=self.device.get(VENDOR),
67
- model=self.device.get(MODEL),
68
- model_id=self.device.get(MODEL_ID),
69
  name=api.smile.name,
70
- sw_version=self.device.get(FIRMWARE),
71
- hw_version=self.device.get(HARDWARE),
72
  )
73
 
74
  # Add extra info if not the gateway device
@@ -81,13 +71,12 @@
81
  )
82
 
83
  @property
 
84
  def available(self) -> bool:
85
  """Return if entity is available."""
86
  return (
87
- # Upstream: Do not change the AVAILABLE line below: some Plugwise devices and zones
88
- # Upstream: do not provide their availability-status!
89
  self._dev_id in self.coordinator.data
90
- and (self.device.get(AVAILABLE, True) is True)
91
  and super().available
92
  )
93
 
 
1
  """Generic Plugwise Entity Class."""
2
 
3
+ from typing import override
4
+
5
+ from plugwise import GwEntityData
6
 
7
  from homeassistant.const import ATTR_NAME, ATTR_VIA_DEVICE, CONF_HOST
8
  from homeassistant.helpers.device_registry import (
 
12
  )
13
  from homeassistant.helpers.update_coordinator import CoordinatorEntity
14
 
15
+ from .const import AVAILABLE, DOMAIN
 
 
 
 
 
 
 
 
 
 
 
 
16
  from .coordinator import PlugwiseDataUpdateCoordinator
17
 
18
 
 
43
 
44
  # Build connections set
45
  connections = set()
46
+ if mac := self.device.get("mac_address"):
47
  connections.add((CONNECTION_NETWORK_MAC, mac))
48
+ if zigbee_mac := self.device.get("zigbee_mac_address"):
49
  connections.add((CONNECTION_ZIGBEE, zigbee_mac))
50
 
51
  # Set base device info
 
53
  configuration_url=configuration_url,
54
  identifiers={(DOMAIN, device_id)},
55
  connections=connections,
56
+ manufacturer=self.device.get("vendor"),
57
+ model=self.device.get("model"),
58
+ model_id=self.device.get("model_id"),
59
  name=api.smile.name,
60
+ sw_version=self.device.get("firmware"),
61
+ hw_version=self.device.get("hardware"),
62
  )
63
 
64
  # Add extra info if not the gateway device
 
71
  )
72
 
73
  @property
74
+ @override
75
  def available(self) -> bool:
76
  """Return if entity is available."""
77
  return (
 
 
78
  self._dev_id in self.coordinator.data
79
+ and (AVAILABLE not in self.device or self.device[AVAILABLE] is True)
80
  and super().available
81
  )
82
 
/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,13 @@
1
  {
2
  "domain": "plugwise",
3
- "name": "Plugwise Beta",
4
  "codeowners": ["@CoMPaTech", "@bouwew"],
5
  "config_flow": true,
6
- "documentation": "https://github.com/plugwise/plugwise-beta",
7
  "integration_type": "hub",
8
  "iot_class": "local_polling",
9
  "loggers": ["plugwise"],
 
10
  "requirements": ["plugwise==1.11.4"],
11
- "version": "0.64.4",
12
  "zeroconf": ["_plugwise._tcp.local."]
13
  }
 
1
  {
2
  "domain": "plugwise",
3
+ "name": "Plugwise",
4
  "codeowners": ["@CoMPaTech", "@bouwew"],
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
+ "quality_scale": "platinum",
11
  "requirements": ["plugwise==1.11.4"],
 
12
  "zeroconf": ["_plugwise._tcp.local."]
13
  }
/home/runner/work/progress/progress/clones/beta/{beta/custom_components → ha-core/homeassistant/components}/plugwise/number.py RENAMED
@@ -1,6 +1,7 @@
1
  """Number platform for Plugwise integration."""
2
 
3
  from dataclasses import dataclass
 
4
 
5
  from homeassistant.components.number import (
6
  NumberDeviceClass,
@@ -12,18 +13,7 @@
12
  from homeassistant.core import HomeAssistant, callback
13
  from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
14
 
15
- from .const import (
16
- LOGGER,
17
- LOWER_BOUND,
18
- MAX_BOILER_TEMP,
19
- MAX_DHW_TEMP,
20
- RESOLUTION,
21
- TEMPERATURE_OFFSET,
22
- UPPER_BOUND,
23
- NumberType,
24
- )
25
-
26
- # Upstream consts
27
  from .coordinator import PlugwiseConfigEntry, PlugwiseDataUpdateCoordinator
28
  from .entity import PlugwiseEntity
29
  from .util import plugwise_command
@@ -38,25 +28,24 @@
38
  key: NumberType
39
 
40
 
41
- # Upstream + is there a reason we didn't rename this one prefixed?
42
  NUMBER_TYPES = (
43
  PlugwiseNumberEntityDescription(
44
- key=MAX_BOILER_TEMP,
45
- translation_key=MAX_BOILER_TEMP,
46
  device_class=NumberDeviceClass.TEMPERATURE,
47
  entity_category=EntityCategory.CONFIG,
48
  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
49
  ),
50
  PlugwiseNumberEntityDescription(
51
- key=MAX_DHW_TEMP,
52
- translation_key=MAX_DHW_TEMP,
53
  device_class=NumberDeviceClass.TEMPERATURE,
54
  entity_category=EntityCategory.CONFIG,
55
  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
56
  ),
57
  PlugwiseNumberEntityDescription(
58
- key=TEMPERATURE_OFFSET,
59
- translation_key=TEMPERATURE_OFFSET,
60
  device_class=NumberDeviceClass.TEMPERATURE,
61
  entity_category=EntityCategory.CONFIG,
62
  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
@@ -69,8 +58,7 @@
69
  entry: PlugwiseConfigEntry,
70
  async_add_entities: AddConfigEntryEntitiesCallback,
71
  ) -> None:
72
- """Set up Plugwise number platform from a config entry."""
73
- # Upstream above to adhere to standard used
74
  coordinator = entry.runtime_data
75
 
76
  @callback
@@ -79,28 +67,12 @@
79
  if not coordinator.new_devices:
80
  return
81
 
82
- # Upstream consts
83
- # async_add_entities(
84
- # PlugwiseNumberEntity(coordinator, device_id, description)
85
- # for device_id in coordinator.new_devices
86
- # for description in NUMBER_TYPES
87
- # if description.key in coordinator.data.devices[device_id]
88
- # )
89
-
90
- # pw-beta alternative for debugging
91
- entities: list[PlugwiseNumberEntity] = []
92
- for device_id in coordinator.new_devices:
93
- device = coordinator.data[device_id]
94
- for description in NUMBER_TYPES:
95
- if description.key in device:
96
- entities.append(
97
- PlugwiseNumberEntity(coordinator, device_id, description)
98
- )
99
- LOGGER.debug(
100
- "Add %s %s number", device["name"], description.translation_key
101
- )
102
-
103
- async_add_entities(entities)
104
 
105
  _add_entities()
106
  entry.async_on_unload(coordinator.async_add_listener(_add_entities))
@@ -119,28 +91,28 @@
119
  ) -> None:
120
  """Initiate Plugwise Number."""
121
  super().__init__(coordinator, device_id)
 
 
 
 
122
  self.device_id = device_id
123
  self.entity_description = description
124
- self._attr_unique_id = f"{device_id}-{description.key}"
125
- self._attr_mode = NumberMode.BOX
126
- ctrl = self.device.get(description.key, {})
127
- self._attr_native_max_value = ctrl.get(UPPER_BOUND, 100.0) # Upstream const
128
- self._attr_native_min_value = ctrl.get(LOWER_BOUND, 0.0) # Upstream const
129
 
130
- native_step = ctrl.get(RESOLUTION, 0.5) # Upstream const
131
- if description.key != TEMPERATURE_OFFSET: # Upstream const
132
  native_step = max(native_step, 0.5)
133
  self._attr_native_step = native_step
134
 
135
  @property
136
- def native_value(self) -> float | None:
 
137
  """Return the present setpoint value."""
138
- return self.device.get(self.entity_description.key, {}).get("setpoint")
139
 
140
  @plugwise_command
 
141
  async def async_set_native_value(self, value: float) -> None:
142
  """Change to the new setpoint value."""
143
- await self.coordinator.api.set_number(self.device_id, self.entity_description.key, value)
144
- LOGGER.debug(
145
- "Setting %s to %s was successful", self.entity_description.key, value
146
  )
 
1
  """Number platform for Plugwise integration."""
2
 
3
  from dataclasses import dataclass
4
+ from typing import override
5
 
6
  from homeassistant.components.number import (
7
  NumberDeviceClass,
 
13
  from homeassistant.core import HomeAssistant, callback
14
  from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
15
 
16
+ from .const import NumberType
 
 
 
 
 
 
 
 
 
 
 
17
  from .coordinator import PlugwiseConfigEntry, PlugwiseDataUpdateCoordinator
18
  from .entity import PlugwiseEntity
19
  from .util import plugwise_command
 
28
  key: NumberType
29
 
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: AddConfigEntryEntitiesCallback,
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[device_id]
75
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
 
77
  _add_entities()
78
  entry.async_on_unload(coordinator.async_add_listener(_add_entities))
 
91
  ) -> None:
92
  """Initiate Plugwise Number."""
93
  super().__init__(coordinator, device_id)
94
+ self._attr_mode = NumberMode.BOX
95
+ self._attr_native_max_value = self.device[description.key]["upper_bound"]
96
+ self._attr_native_min_value = self.device[description.key]["lower_bound"]
97
+ self._attr_unique_id = f"{device_id}-{description.key}"
98
  self.device_id = device_id
99
  self.entity_description = description
 
 
 
 
 
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
 
106
  @property
107
+ @override
108
+ def native_value(self) -> float:
109
  """Return the present setpoint value."""
110
+ return self.device[self.entity_description.key]["setpoint"]
111
 
112
  @plugwise_command
113
+ @override
114
  async def async_set_native_value(self, value: float) -> None:
115
  """Change to the new setpoint value."""
116
+ await self.coordinator.api.set_number(
117
+ self.device_id, self.entity_description.key, value
 
118
  )
/home/runner/work/progress/progress/clones/beta/{beta/custom_components → ha-core/homeassistant/components}/plugwise/select.py RENAMED
@@ -1,6 +1,7 @@
1
  """Plugwise Select component for Home Assistant."""
2
 
3
  from dataclasses import dataclass
 
4
 
5
  from homeassistant.components.select import SelectEntity, SelectEntityDescription
6
  from homeassistant.const import STATE_ON, EntityCategory
@@ -8,23 +9,14 @@
8
  from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
9
 
10
  from .const import (
11
- AVAILABLE_SCHEDULES,
12
- DHW_MODES,
13
- GATEWAY_MODES,
14
- LOCATION,
15
- LOGGER,
16
- REGULATION_MODES,
17
  SELECT_DHW_MODE,
18
  SELECT_GATEWAY_MODE,
19
  SELECT_REGULATION_MODE,
20
  SELECT_SCHEDULE,
21
  SELECT_ZONE_PROFILE,
22
- ZONE_PROFILES,
23
  SelectOptionsType,
24
  SelectType,
25
  )
26
-
27
- # Upstream consts
28
  from .coordinator import PlugwiseConfigEntry, PlugwiseDataUpdateCoordinator
29
  from .entity import PlugwiseEntity
30
  from .util import plugwise_command
@@ -40,36 +32,35 @@
40
  options_key: SelectOptionsType
41
 
42
 
43
- # Upstream
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=SELECT_REGULATION_MODE,
53
  entity_category=EntityCategory.CONFIG,
54
- options_key=REGULATION_MODES,
55
  ),
56
  PlugwiseSelectEntityDescription(
57
  key=SELECT_DHW_MODE,
58
  translation_key=SELECT_DHW_MODE,
59
  entity_category=EntityCategory.CONFIG,
60
- options_key=DHW_MODES,
61
  ),
62
  PlugwiseSelectEntityDescription(
63
  key=SELECT_GATEWAY_MODE,
64
  translation_key=SELECT_GATEWAY_MODE,
65
  entity_category=EntityCategory.CONFIG,
66
- options_key=GATEWAY_MODES,
67
  ),
68
  PlugwiseSelectEntityDescription(
69
  key=SELECT_ZONE_PROFILE,
70
  translation_key=SELECT_ZONE_PROFILE,
71
  entity_category=EntityCategory.CONFIG,
72
- options_key=ZONE_PROFILES,
73
  ),
74
  )
75
 
@@ -79,7 +70,7 @@
79
  entry: PlugwiseConfigEntry,
80
  async_add_entities: AddConfigEntryEntitiesCallback,
81
  ) -> None:
82
- """Set up Plugwise selector from a config entry."""
83
  coordinator = entry.runtime_data
84
 
85
  @callback
@@ -88,27 +79,12 @@
88
  if not coordinator.new_devices:
89
  return
90
 
91
- # Upstream consts
92
- # async_add_entities(
93
- # PlugwiseSelectEntity(coordinator, device_id, description)
94
- # for device_id in coordinator.new_devices
95
- # for description in SELECT_TYPES
96
- # if description.options_key in coordinator.data.devices[device_id]
97
- # )
98
- # pw-beta alternative for debugging
99
- entities: list[PlugwiseSelectEntity] = []
100
- for device_id in coordinator.new_devices:
101
- device = coordinator.data[device_id]
102
- for description in SELECT_TYPES:
103
- if device.get(description.options_key):
104
- entities.append(
105
- PlugwiseSelectEntity(coordinator, device_id, description)
106
- )
107
- LOGGER.debug(
108
- "Add %s %s selector", device["name"], description.translation_key
109
- )
110
-
111
- async_add_entities(entities)
112
 
113
  _add_entities()
114
  entry.async_on_unload(coordinator.async_add_listener(_add_entities))
@@ -131,30 +107,28 @@
131
  self.entity_description = entity_description
132
 
133
  self._location = device_id
134
- if (location := self.device.get(LOCATION)) is not None:
135
  self._location = location
136
 
137
  @property
 
138
  def current_option(self) -> str | None:
139
  """Return the selected entity option to represent the entity state."""
140
- return self.device.get(self.entity_description.key)
141
 
142
  @property
 
143
  def options(self) -> list[str]:
144
  """Return the available select-options."""
145
- return self.device.get(self.entity_description.options_key, [])
146
 
147
  @plugwise_command
 
148
  async def async_select_option(self, option: str) -> None:
149
  """Change to the selected entity option.
150
 
151
- Location ID and STATE_ON are required for the thermostat-schedule select.
152
  """
153
  await self.coordinator.api.set_select(
154
  self.entity_description.key, self._location, option, STATE_ON
155
  )
156
- LOGGER.debug(
157
- "Set %s to %s was successful",
158
- self.entity_description.key,
159
- option,
160
- )
 
1
  """Plugwise Select component for Home Assistant."""
2
 
3
  from dataclasses import dataclass
4
+ from typing import override
5
 
6
  from homeassistant.components.select import SelectEntity, SelectEntityDescription
7
  from homeassistant.const import STATE_ON, EntityCategory
 
9
  from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
10
 
11
  from .const import (
 
 
 
 
 
 
12
  SELECT_DHW_MODE,
13
  SELECT_GATEWAY_MODE,
14
  SELECT_REGULATION_MODE,
15
  SELECT_SCHEDULE,
16
  SELECT_ZONE_PROFILE,
 
17
  SelectOptionsType,
18
  SelectType,
19
  )
 
 
20
  from .coordinator import PlugwiseConfigEntry, PlugwiseDataUpdateCoordinator
21
  from .entity import PlugwiseEntity
22
  from .util import plugwise_command
 
32
  options_key: SelectOptionsType
33
 
34
 
 
35
  SELECT_TYPES = (
36
  PlugwiseSelectEntityDescription(
37
  key=SELECT_SCHEDULE,
38
  translation_key=SELECT_SCHEDULE,
39
+ options_key="available_schedules",
40
  ),
41
  PlugwiseSelectEntityDescription(
42
  key=SELECT_REGULATION_MODE,
43
  translation_key=SELECT_REGULATION_MODE,
44
  entity_category=EntityCategory.CONFIG,
45
+ options_key="regulation_modes",
46
  ),
47
  PlugwiseSelectEntityDescription(
48
  key=SELECT_DHW_MODE,
49
  translation_key=SELECT_DHW_MODE,
50
  entity_category=EntityCategory.CONFIG,
51
+ options_key="dhw_modes",
52
  ),
53
  PlugwiseSelectEntityDescription(
54
  key=SELECT_GATEWAY_MODE,
55
  translation_key=SELECT_GATEWAY_MODE,
56
  entity_category=EntityCategory.CONFIG,
57
+ options_key="gateway_modes",
58
  ),
59
  PlugwiseSelectEntityDescription(
60
  key=SELECT_ZONE_PROFILE,
61
  translation_key=SELECT_ZONE_PROFILE,
62
  entity_category=EntityCategory.CONFIG,
63
+ options_key="zone_profiles",
64
  ),
65
  )
66
 
 
70
  entry: PlugwiseConfigEntry,
71
  async_add_entities: AddConfigEntryEntitiesCallback,
72
  ) -> None:
73
+ """Set up the Smile selector from a config entry."""
74
  coordinator = entry.runtime_data
75
 
76
  @callback
 
79
  if not coordinator.new_devices:
80
  return
81
 
82
+ async_add_entities(
83
+ PlugwiseSelectEntity(coordinator, device_id, description)
84
+ for device_id in coordinator.new_devices
85
+ for description in SELECT_TYPES
86
+ if coordinator.data[device_id].get(description.options_key)
87
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88
 
89
  _add_entities()
90
  entry.async_on_unload(coordinator.async_add_listener(_add_entities))
 
107
  self.entity_description = entity_description
108
 
109
  self._location = device_id
110
+ if (location := self.device.get("location")) is not None:
111
  self._location = location
112
 
113
  @property
114
+ @override
115
  def current_option(self) -> str | None:
116
  """Return the selected entity option to represent the entity state."""
117
+ return self.device[self.entity_description.key]
118
 
119
  @property
120
+ @override
121
  def options(self) -> list[str]:
122
  """Return the available select-options."""
123
+ return self.device[self.entity_description.options_key]
124
 
125
  @plugwise_command
126
+ @override
127
  async def async_select_option(self, option: str) -> None:
128
  """Change to the selected entity option.
129
 
130
+ self._location and STATE_ON are required for the thermostat-schedule select.
131
  """
132
  await self.coordinator.api.set_select(
133
  self.entity_description.key, self._location, option, STATE_ON
134
  )
 
 
 
 
 
/home/runner/work/progress/progress/clones/beta/{beta/custom_components → ha-core/homeassistant/components}/plugwise/sensor.py RENAMED
@@ -1,6 +1,7 @@
1
  """Plugwise Sensor component for Home Assistant."""
2
 
3
  from dataclasses import dataclass
 
4
 
5
  from plugwise.constants import SensorType
6
 
@@ -11,7 +12,6 @@
11
  SensorStateClass,
12
  )
13
  from homeassistant.const import (
14
- ATTR_TEMPERATURE, # Upstream
15
  LIGHT_LUX,
16
  PERCENTAGE,
17
  EntityCategory,
@@ -26,57 +26,6 @@
26
  from homeassistant.core import HomeAssistant, callback
27
  from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
28
 
29
- from .const import (
30
- DHW_SETPOINT,
31
- DHW_TEMP,
32
- EL_CONS_INTERVAL,
33
- EL_CONS_OP_CUMULATIVE,
34
- EL_CONS_OP_INTERVAL,
35
- EL_CONS_OP_POINT,
36
- EL_CONS_P_CUMULATIVE,
37
- EL_CONS_P_INTERVAL,
38
- EL_CONS_P_POINT,
39
- EL_CONS_POINT,
40
- EL_CONSUMED,
41
- EL_PH1_CONSUMED,
42
- EL_PH1_PRODUCED,
43
- EL_PH2_CONSUMED,
44
- EL_PH2_PRODUCED,
45
- EL_PH3_CONSUMED,
46
- EL_PH3_PRODUCED,
47
- EL_PROD_INTERVAL,
48
- EL_PROD_OP_CUMULATIVE,
49
- EL_PROD_OP_INTERVAL,
50
- EL_PROD_OP_POINT,
51
- EL_PROD_P_CUMULATIVE,
52
- EL_PROD_P_INTERVAL,
53
- EL_PROD_P_POINT,
54
- EL_PROD_POINT,
55
- EL_PRODUCED,
56
- GAS_CONS_CUMULATIVE,
57
- GAS_CONS_INTERVAL,
58
- INTENDED_BOILER_TEMP,
59
- LOGGER, # pw-beta
60
- MOD_LEVEL,
61
- NET_EL_CUMULATIVE,
62
- NET_EL_POINT,
63
- OUTDOOR_AIR_TEMP,
64
- OUTDOOR_TEMP,
65
- RETURN_TEMP,
66
- SENSORS,
67
- TARGET_TEMP,
68
- TARGET_TEMP_HIGH,
69
- TARGET_TEMP_LOW,
70
- TEMP_DIFF,
71
- VALVE_POS,
72
- VOLTAGE_PH1,
73
- VOLTAGE_PH2,
74
- VOLTAGE_PH3,
75
- WATER_PRESSURE,
76
- WATER_TEMP,
77
- )
78
-
79
- # Upstream consts
80
  from .coordinator import PlugwiseConfigEntry, PlugwiseDataUpdateCoordinator
81
  from .entity import PlugwiseEntity
82
 
@@ -91,301 +40,299 @@
91
  key: SensorType
92
 
93
 
94
- # Upstream consts
95
- PLUGWISE_SENSORS: tuple[PlugwiseSensorEntityDescription, ...] = (
96
  PlugwiseSensorEntityDescription(
97
- key=TARGET_TEMP,
98
- translation_key=TARGET_TEMP,
99
  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
100
  device_class=SensorDeviceClass.TEMPERATURE,
101
  state_class=SensorStateClass.MEASUREMENT,
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,
108
  state_class=SensorStateClass.MEASUREMENT,
109
  ),
110
  PlugwiseSensorEntityDescription(
111
- key=TARGET_TEMP_LOW,
112
  translation_key="heating_setpoint",
113
  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
114
  device_class=SensorDeviceClass.TEMPERATURE,
115
  state_class=SensorStateClass.MEASUREMENT,
116
  ),
117
  PlugwiseSensorEntityDescription(
118
- key=ATTR_TEMPERATURE,
119
  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
120
  device_class=SensorDeviceClass.TEMPERATURE,
121
  state_class=SensorStateClass.MEASUREMENT,
122
  ),
123
  PlugwiseSensorEntityDescription(
124
- key=INTENDED_BOILER_TEMP,
125
- translation_key=INTENDED_BOILER_TEMP,
126
  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
127
  device_class=SensorDeviceClass.TEMPERATURE,
128
  entity_category=EntityCategory.DIAGNOSTIC,
129
  state_class=SensorStateClass.MEASUREMENT,
130
  ),
131
  PlugwiseSensorEntityDescription(
132
- key=TEMP_DIFF,
133
- translation_key=TEMP_DIFF,
134
  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
135
  device_class=SensorDeviceClass.TEMPERATURE,
136
  entity_category=EntityCategory.DIAGNOSTIC,
137
  state_class=SensorStateClass.MEASUREMENT,
138
  ),
139
  PlugwiseSensorEntityDescription(
140
- key=OUTDOOR_TEMP,
141
- translation_key=OUTDOOR_TEMP,
142
  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
143
  device_class=SensorDeviceClass.TEMPERATURE,
144
  entity_category=EntityCategory.DIAGNOSTIC,
145
  state_class=SensorStateClass.MEASUREMENT,
146
- suggested_display_precision=1,
147
  ),
148
  PlugwiseSensorEntityDescription(
149
- key=OUTDOOR_AIR_TEMP,
150
- translation_key=OUTDOOR_AIR_TEMP,
151
  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
152
  device_class=SensorDeviceClass.TEMPERATURE,
153
  entity_category=EntityCategory.DIAGNOSTIC,
154
  state_class=SensorStateClass.MEASUREMENT,
155
  ),
156
  PlugwiseSensorEntityDescription(
157
- key=WATER_TEMP,
158
- translation_key=WATER_TEMP,
159
  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
160
  device_class=SensorDeviceClass.TEMPERATURE,
161
  entity_category=EntityCategory.DIAGNOSTIC,
162
  state_class=SensorStateClass.MEASUREMENT,
163
  ),
164
  PlugwiseSensorEntityDescription(
165
- key=RETURN_TEMP,
166
- translation_key=RETURN_TEMP,
167
  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
168
  device_class=SensorDeviceClass.TEMPERATURE,
169
  entity_category=EntityCategory.DIAGNOSTIC,
170
  state_class=SensorStateClass.MEASUREMENT,
171
  ),
172
  PlugwiseSensorEntityDescription(
173
- key=EL_CONSUMED,
174
- translation_key=EL_CONSUMED,
175
  native_unit_of_measurement=UnitOfPower.WATT,
176
  device_class=SensorDeviceClass.POWER,
177
  state_class=SensorStateClass.MEASUREMENT,
178
  ),
179
  PlugwiseSensorEntityDescription(
180
- key=EL_PRODUCED,
181
- translation_key=EL_PRODUCED,
182
  native_unit_of_measurement=UnitOfPower.WATT,
183
  device_class=SensorDeviceClass.POWER,
184
  state_class=SensorStateClass.MEASUREMENT,
185
  entity_registry_enabled_default=False,
186
  ),
187
  PlugwiseSensorEntityDescription(
188
- key=EL_CONS_INTERVAL,
189
- translation_key=EL_CONS_INTERVAL,
190
  native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
191
  device_class=SensorDeviceClass.ENERGY,
192
  state_class=SensorStateClass.TOTAL,
193
  ),
194
  PlugwiseSensorEntityDescription(
195
- key=EL_CONS_P_INTERVAL,
196
- translation_key=EL_CONS_P_INTERVAL,
197
  native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
198
  device_class=SensorDeviceClass.ENERGY,
199
  state_class=SensorStateClass.TOTAL,
200
  ),
201
  PlugwiseSensorEntityDescription(
202
- key=EL_CONS_OP_INTERVAL,
203
- translation_key=EL_CONS_OP_INTERVAL,
204
  native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
205
  device_class=SensorDeviceClass.ENERGY,
206
  state_class=SensorStateClass.TOTAL,
207
  ),
208
  PlugwiseSensorEntityDescription(
209
- key=EL_PROD_INTERVAL,
210
- translation_key=EL_PROD_INTERVAL,
211
  native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
212
  device_class=SensorDeviceClass.ENERGY,
213
  state_class=SensorStateClass.TOTAL,
214
  entity_registry_enabled_default=False,
215
  ),
216
  PlugwiseSensorEntityDescription(
217
- key=EL_PROD_P_INTERVAL,
218
- translation_key=EL_PROD_P_INTERVAL,
219
  native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
220
  device_class=SensorDeviceClass.ENERGY,
221
  state_class=SensorStateClass.TOTAL,
222
  ),
223
  PlugwiseSensorEntityDescription(
224
- key=EL_PROD_OP_INTERVAL,
225
- translation_key=EL_PROD_OP_INTERVAL,
226
  native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
227
  device_class=SensorDeviceClass.ENERGY,
228
  state_class=SensorStateClass.TOTAL,
229
  ),
230
  PlugwiseSensorEntityDescription(
231
- key=EL_CONS_POINT,
232
- translation_key=EL_CONS_POINT,
233
  device_class=SensorDeviceClass.POWER,
234
  native_unit_of_measurement=UnitOfPower.WATT,
235
  state_class=SensorStateClass.MEASUREMENT,
236
  ),
237
  PlugwiseSensorEntityDescription(
238
- key=EL_CONS_OP_POINT,
239
- translation_key=EL_CONS_OP_POINT,
240
  native_unit_of_measurement=UnitOfPower.WATT,
241
  device_class=SensorDeviceClass.POWER,
242
  state_class=SensorStateClass.MEASUREMENT,
243
  ),
244
  PlugwiseSensorEntityDescription(
245
- key=EL_CONS_P_POINT,
246
- translation_key=EL_CONS_P_POINT,
247
  native_unit_of_measurement=UnitOfPower.WATT,
248
  device_class=SensorDeviceClass.POWER,
249
  state_class=SensorStateClass.MEASUREMENT,
250
  ),
251
  PlugwiseSensorEntityDescription(
252
- key=EL_CONS_OP_CUMULATIVE,
253
- translation_key=EL_CONS_OP_CUMULATIVE,
254
  native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
255
  device_class=SensorDeviceClass.ENERGY,
256
  state_class=SensorStateClass.TOTAL_INCREASING,
257
  ),
258
  PlugwiseSensorEntityDescription(
259
- key=EL_CONS_P_CUMULATIVE,
260
- translation_key=EL_CONS_P_CUMULATIVE,
261
  native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
262
  device_class=SensorDeviceClass.ENERGY,
263
  state_class=SensorStateClass.TOTAL_INCREASING,
264
  ),
265
  PlugwiseSensorEntityDescription(
266
- key=EL_PROD_POINT,
267
- translation_key=EL_PROD_POINT,
268
  device_class=SensorDeviceClass.POWER,
269
  native_unit_of_measurement=UnitOfPower.WATT,
270
  state_class=SensorStateClass.MEASUREMENT,
271
  ),
272
  PlugwiseSensorEntityDescription(
273
- key=EL_PROD_OP_POINT,
274
- translation_key=EL_PROD_OP_POINT,
275
  native_unit_of_measurement=UnitOfPower.WATT,
276
  device_class=SensorDeviceClass.POWER,
277
  state_class=SensorStateClass.MEASUREMENT,
278
  ),
279
  PlugwiseSensorEntityDescription(
280
- key=EL_PROD_P_POINT,
281
- translation_key=EL_PROD_P_POINT,
282
  native_unit_of_measurement=UnitOfPower.WATT,
283
  device_class=SensorDeviceClass.POWER,
284
  state_class=SensorStateClass.MEASUREMENT,
285
  ),
286
  PlugwiseSensorEntityDescription(
287
- key=EL_PROD_OP_CUMULATIVE,
288
- translation_key=EL_PROD_OP_CUMULATIVE,
289
  native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
290
  device_class=SensorDeviceClass.ENERGY,
291
  state_class=SensorStateClass.TOTAL_INCREASING,
292
  ),
293
  PlugwiseSensorEntityDescription(
294
- key=EL_PROD_P_CUMULATIVE,
295
- translation_key=EL_PROD_P_CUMULATIVE,
296
  native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
297
  device_class=SensorDeviceClass.ENERGY,
298
  state_class=SensorStateClass.TOTAL_INCREASING,
299
  ),
300
  PlugwiseSensorEntityDescription(
301
- key=EL_PH1_CONSUMED,
302
- translation_key=EL_PH1_CONSUMED,
303
  device_class=SensorDeviceClass.POWER,
304
  native_unit_of_measurement=UnitOfPower.WATT,
305
  state_class=SensorStateClass.MEASUREMENT,
306
  ),
307
  PlugwiseSensorEntityDescription(
308
- key=EL_PH2_CONSUMED,
309
- translation_key=EL_PH2_CONSUMED,
310
  device_class=SensorDeviceClass.POWER,
311
  native_unit_of_measurement=UnitOfPower.WATT,
312
  state_class=SensorStateClass.MEASUREMENT,
313
  ),
314
  PlugwiseSensorEntityDescription(
315
- key=EL_PH3_CONSUMED,
316
- translation_key=EL_PH3_CONSUMED,
317
  device_class=SensorDeviceClass.POWER,
318
  native_unit_of_measurement=UnitOfPower.WATT,
319
  state_class=SensorStateClass.MEASUREMENT,
320
  ),
321
  PlugwiseSensorEntityDescription(
322
- key=EL_PH1_PRODUCED,
323
- translation_key=EL_PH1_PRODUCED,
324
  device_class=SensorDeviceClass.POWER,
325
  native_unit_of_measurement=UnitOfPower.WATT,
326
  state_class=SensorStateClass.MEASUREMENT,
327
  ),
328
  PlugwiseSensorEntityDescription(
329
- key=EL_PH2_PRODUCED,
330
- translation_key=EL_PH2_PRODUCED,
331
  device_class=SensorDeviceClass.POWER,
332
  native_unit_of_measurement=UnitOfPower.WATT,
333
  state_class=SensorStateClass.MEASUREMENT,
334
  ),
335
  PlugwiseSensorEntityDescription(
336
- key=EL_PH3_PRODUCED,
337
- translation_key=EL_PH3_PRODUCED,
338
  device_class=SensorDeviceClass.POWER,
339
  native_unit_of_measurement=UnitOfPower.WATT,
340
  state_class=SensorStateClass.MEASUREMENT,
341
  ),
342
  PlugwiseSensorEntityDescription(
343
- key=VOLTAGE_PH1,
344
- translation_key=VOLTAGE_PH1,
345
  device_class=SensorDeviceClass.VOLTAGE,
346
  native_unit_of_measurement=UnitOfElectricPotential.VOLT,
347
  state_class=SensorStateClass.MEASUREMENT,
348
  entity_registry_enabled_default=False,
349
  ),
350
  PlugwiseSensorEntityDescription(
351
- key=VOLTAGE_PH2,
352
- translation_key=VOLTAGE_PH2,
353
  device_class=SensorDeviceClass.VOLTAGE,
354
  native_unit_of_measurement=UnitOfElectricPotential.VOLT,
355
  state_class=SensorStateClass.MEASUREMENT,
356
  entity_registry_enabled_default=False,
357
  ),
358
  PlugwiseSensorEntityDescription(
359
- key=VOLTAGE_PH3,
360
- translation_key=VOLTAGE_PH3,
361
  device_class=SensorDeviceClass.VOLTAGE,
362
  native_unit_of_measurement=UnitOfElectricPotential.VOLT,
363
  state_class=SensorStateClass.MEASUREMENT,
364
  entity_registry_enabled_default=False,
365
  ),
366
  PlugwiseSensorEntityDescription(
367
- key=GAS_CONS_INTERVAL,
368
- translation_key=GAS_CONS_INTERVAL,
369
  native_unit_of_measurement=UnitOfVolumeFlowRate.CUBIC_METERS_PER_HOUR,
370
  state_class=SensorStateClass.MEASUREMENT,
371
  ),
372
  PlugwiseSensorEntityDescription(
373
- key=GAS_CONS_CUMULATIVE,
374
- translation_key=GAS_CONS_CUMULATIVE,
375
  native_unit_of_measurement=UnitOfVolume.CUBIC_METERS,
376
  device_class=SensorDeviceClass.GAS,
377
  state_class=SensorStateClass.TOTAL,
378
  ),
379
  PlugwiseSensorEntityDescription(
380
- key=NET_EL_POINT,
381
- translation_key=NET_EL_POINT,
382
  native_unit_of_measurement=UnitOfPower.WATT,
383
  device_class=SensorDeviceClass.POWER,
384
  state_class=SensorStateClass.MEASUREMENT,
385
  ),
386
  PlugwiseSensorEntityDescription(
387
- key=NET_EL_CUMULATIVE,
388
- translation_key=NET_EL_CUMULATIVE,
389
  native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
390
  device_class=SensorDeviceClass.ENERGY,
391
  state_class=SensorStateClass.TOTAL,
@@ -405,22 +352,22 @@
405
  state_class=SensorStateClass.MEASUREMENT,
406
  ),
407
  PlugwiseSensorEntityDescription(
408
- key=MOD_LEVEL,
409
- translation_key=MOD_LEVEL,
410
  native_unit_of_measurement=PERCENTAGE,
411
  entity_category=EntityCategory.DIAGNOSTIC,
412
  state_class=SensorStateClass.MEASUREMENT,
413
  ),
414
  PlugwiseSensorEntityDescription(
415
- key=VALVE_POS,
416
- translation_key=VALVE_POS,
417
  native_unit_of_measurement=PERCENTAGE,
418
  entity_category=EntityCategory.DIAGNOSTIC,
419
  state_class=SensorStateClass.MEASUREMENT,
420
  ),
421
  PlugwiseSensorEntityDescription(
422
- key=WATER_PRESSURE,
423
- translation_key=WATER_PRESSURE,
424
  native_unit_of_measurement=UnitOfPressure.BAR,
425
  device_class=SensorDeviceClass.PRESSURE,
426
  entity_category=EntityCategory.DIAGNOSTIC,
@@ -433,16 +380,16 @@
433
  state_class=SensorStateClass.MEASUREMENT,
434
  ),
435
  PlugwiseSensorEntityDescription(
436
- key=DHW_TEMP,
437
- translation_key=DHW_TEMP,
438
  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
439
  device_class=SensorDeviceClass.TEMPERATURE,
440
  entity_category=EntityCategory.DIAGNOSTIC,
441
  state_class=SensorStateClass.MEASUREMENT,
442
  ),
443
  PlugwiseSensorEntityDescription(
444
- key=DHW_SETPOINT,
445
- translation_key=DHW_SETPOINT,
446
  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
447
  device_class=SensorDeviceClass.TEMPERATURE,
448
  entity_category=EntityCategory.DIAGNOSTIC,
@@ -456,8 +403,7 @@
456
  entry: PlugwiseConfigEntry,
457
  async_add_entities: AddConfigEntryEntitiesCallback,
458
  ) -> None:
459
- """Set up Plugwise sensors from a config entry."""
460
- # Upstream as Plugwise not Smile
461
  coordinator = entry.runtime_data
462
 
463
  @callback
@@ -466,29 +412,13 @@
466
  if not coordinator.new_devices:
467
  return
468
 
469
- # Upstream consts
470
- # async_add_entities(
471
- # PlugwiseSensorEntity(coordinator, device_id, description)
472
- # for device_id in coordinator.new_devices
473
- # if (sensors := coordinator.data.devices[device_id].get(SENSORS))
474
- # for description in PLUGWISE_SENSORS
475
- # if description.key in sensors
476
- # )
477
- # pw-beta alternative for debugging
478
- entities: list[PlugwiseSensorEntity] = []
479
- for device_id in coordinator.new_devices:
480
- device = coordinator.data[device_id]
481
- if not (sensors := device.get(SENSORS)):
482
- continue
483
- for description in PLUGWISE_SENSORS:
484
- if description.key not in sensors:
485
- continue
486
- entities.append(PlugwiseSensorEntity(coordinator, device_id, description))
487
- LOGGER.debug(
488
- "Add %s %s sensor", device["name"], description.translation_key or description.key
489
- )
490
-
491
- async_add_entities(entities)
492
 
493
  _add_entities()
494
  entry.async_on_unload(coordinator.async_add_listener(_add_entities))
@@ -507,10 +437,11 @@
507
  ) -> None:
508
  """Initialise the sensor."""
509
  super().__init__(coordinator, device_id)
510
- self.entity_description = description
511
  self._attr_unique_id = f"{device_id}-{description.key}"
 
512
 
513
  @property
514
- def native_value(self) -> int | float | None:
 
515
  """Return the value reported by the sensor."""
516
- return self.device.get(SENSORS, {}).get(self.entity_description.key) # Upstream consts
 
1
  """Plugwise Sensor component for Home Assistant."""
2
 
3
  from dataclasses import dataclass
4
+ from typing import override
5
 
6
  from plugwise.constants import SensorType
7
 
 
12
  SensorStateClass,
13
  )
14
  from homeassistant.const import (
 
15
  LIGHT_LUX,
16
  PERCENTAGE,
17
  EntityCategory,
 
26
  from homeassistant.core import HomeAssistant, callback
27
  from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
28
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  from .coordinator import PlugwiseConfigEntry, PlugwiseDataUpdateCoordinator
30
  from .entity import PlugwiseEntity
31
 
 
40
  key: SensorType
41
 
42
 
43
+ SENSORS: tuple[PlugwiseSensorEntityDescription, ...] = (
 
44
  PlugwiseSensorEntityDescription(
45
+ key="setpoint",
46
+ translation_key="setpoint",
47
  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
48
  device_class=SensorDeviceClass.TEMPERATURE,
49
  state_class=SensorStateClass.MEASUREMENT,
50
  ),
51
  PlugwiseSensorEntityDescription(
52
+ key="setpoint_high",
53
  translation_key="cooling_setpoint",
54
  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
55
  device_class=SensorDeviceClass.TEMPERATURE,
56
  state_class=SensorStateClass.MEASUREMENT,
57
  ),
58
  PlugwiseSensorEntityDescription(
59
+ key="setpoint_low",
60
  translation_key="heating_setpoint",
61
  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
62
  device_class=SensorDeviceClass.TEMPERATURE,
63
  state_class=SensorStateClass.MEASUREMENT,
64
  ),
65
  PlugwiseSensorEntityDescription(
66
+ key="temperature",
67
  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
68
  device_class=SensorDeviceClass.TEMPERATURE,
69
  state_class=SensorStateClass.MEASUREMENT,
70
  ),
71
  PlugwiseSensorEntityDescription(
72
+ key="intended_boiler_temperature",
73
+ translation_key="intended_boiler_temperature",
74
  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
75
  device_class=SensorDeviceClass.TEMPERATURE,
76
  entity_category=EntityCategory.DIAGNOSTIC,
77
  state_class=SensorStateClass.MEASUREMENT,
78
  ),
79
  PlugwiseSensorEntityDescription(
80
+ key="temperature_difference",
81
+ translation_key="temperature_difference",
82
  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
83
  device_class=SensorDeviceClass.TEMPERATURE,
84
  entity_category=EntityCategory.DIAGNOSTIC,
85
  state_class=SensorStateClass.MEASUREMENT,
86
  ),
87
  PlugwiseSensorEntityDescription(
88
+ key="outdoor_temperature",
89
+ translation_key="outdoor_temperature",
90
  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
91
  device_class=SensorDeviceClass.TEMPERATURE,
92
  entity_category=EntityCategory.DIAGNOSTIC,
93
  state_class=SensorStateClass.MEASUREMENT,
 
94
  ),
95
  PlugwiseSensorEntityDescription(
96
+ key="outdoor_air_temperature",
97
+ translation_key="outdoor_air_temperature",
98
  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
99
  device_class=SensorDeviceClass.TEMPERATURE,
100
  entity_category=EntityCategory.DIAGNOSTIC,
101
  state_class=SensorStateClass.MEASUREMENT,
102
  ),
103
  PlugwiseSensorEntityDescription(
104
+ key="water_temperature",
105
+ translation_key="water_temperature",
106
  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
107
  device_class=SensorDeviceClass.TEMPERATURE,
108
  entity_category=EntityCategory.DIAGNOSTIC,
109
  state_class=SensorStateClass.MEASUREMENT,
110
  ),
111
  PlugwiseSensorEntityDescription(
112
+ key="return_temperature",
113
+ translation_key="return_temperature",
114
  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
115
  device_class=SensorDeviceClass.TEMPERATURE,
116
  entity_category=EntityCategory.DIAGNOSTIC,
117
  state_class=SensorStateClass.MEASUREMENT,
118
  ),
119
  PlugwiseSensorEntityDescription(
120
+ key="electricity_consumed",
121
+ translation_key="electricity_consumed",
122
  native_unit_of_measurement=UnitOfPower.WATT,
123
  device_class=SensorDeviceClass.POWER,
124
  state_class=SensorStateClass.MEASUREMENT,
125
  ),
126
  PlugwiseSensorEntityDescription(
127
+ key="electricity_produced",
128
+ translation_key="electricity_produced",
129
  native_unit_of_measurement=UnitOfPower.WATT,
130
  device_class=SensorDeviceClass.POWER,
131
  state_class=SensorStateClass.MEASUREMENT,
132
  entity_registry_enabled_default=False,
133
  ),
134
  PlugwiseSensorEntityDescription(
135
+ key="electricity_consumed_interval",
136
+ translation_key="electricity_consumed_interval",
137
  native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
138
  device_class=SensorDeviceClass.ENERGY,
139
  state_class=SensorStateClass.TOTAL,
140
  ),
141
  PlugwiseSensorEntityDescription(
142
+ key="electricity_consumed_peak_interval",
143
+ translation_key="electricity_consumed_peak_interval",
144
  native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
145
  device_class=SensorDeviceClass.ENERGY,
146
  state_class=SensorStateClass.TOTAL,
147
  ),
148
  PlugwiseSensorEntityDescription(
149
+ key="electricity_consumed_off_peak_interval",
150
+ translation_key="electricity_consumed_off_peak_interval",
151
  native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
152
  device_class=SensorDeviceClass.ENERGY,
153
  state_class=SensorStateClass.TOTAL,
154
  ),
155
  PlugwiseSensorEntityDescription(
156
+ key="electricity_produced_interval",
157
+ translation_key="electricity_produced_interval",
158
  native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
159
  device_class=SensorDeviceClass.ENERGY,
160
  state_class=SensorStateClass.TOTAL,
161
  entity_registry_enabled_default=False,
162
  ),
163
  PlugwiseSensorEntityDescription(
164
+ key="electricity_produced_peak_interval",
165
+ translation_key="electricity_produced_peak_interval",
166
  native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
167
  device_class=SensorDeviceClass.ENERGY,
168
  state_class=SensorStateClass.TOTAL,
169
  ),
170
  PlugwiseSensorEntityDescription(
171
+ key="electricity_produced_off_peak_interval",
172
+ translation_key="electricity_produced_off_peak_interval",
173
  native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
174
  device_class=SensorDeviceClass.ENERGY,
175
  state_class=SensorStateClass.TOTAL,
176
  ),
177
  PlugwiseSensorEntityDescription(
178
+ key="electricity_consumed_point",
179
+ translation_key="electricity_consumed_point",
180
  device_class=SensorDeviceClass.POWER,
181
  native_unit_of_measurement=UnitOfPower.WATT,
182
  state_class=SensorStateClass.MEASUREMENT,
183
  ),
184
  PlugwiseSensorEntityDescription(
185
+ key="electricity_consumed_off_peak_point",
186
+ translation_key="electricity_consumed_off_peak_point",
187
  native_unit_of_measurement=UnitOfPower.WATT,
188
  device_class=SensorDeviceClass.POWER,
189
  state_class=SensorStateClass.MEASUREMENT,
190
  ),
191
  PlugwiseSensorEntityDescription(
192
+ key="electricity_consumed_peak_point",
193
+ translation_key="electricity_consumed_peak_point",
194
  native_unit_of_measurement=UnitOfPower.WATT,
195
  device_class=SensorDeviceClass.POWER,
196
  state_class=SensorStateClass.MEASUREMENT,
197
  ),
198
  PlugwiseSensorEntityDescription(
199
+ key="electricity_consumed_off_peak_cumulative",
200
+ translation_key="electricity_consumed_off_peak_cumulative",
201
  native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
202
  device_class=SensorDeviceClass.ENERGY,
203
  state_class=SensorStateClass.TOTAL_INCREASING,
204
  ),
205
  PlugwiseSensorEntityDescription(
206
+ key="electricity_consumed_peak_cumulative",
207
+ translation_key="electricity_consumed_peak_cumulative",
208
  native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
209
  device_class=SensorDeviceClass.ENERGY,
210
  state_class=SensorStateClass.TOTAL_INCREASING,
211
  ),
212
  PlugwiseSensorEntityDescription(
213
+ key="electricity_produced_point",
214
+ translation_key="electricity_produced_point",
215
  device_class=SensorDeviceClass.POWER,
216
  native_unit_of_measurement=UnitOfPower.WATT,
217
  state_class=SensorStateClass.MEASUREMENT,
218
  ),
219
  PlugwiseSensorEntityDescription(
220
+ key="electricity_produced_off_peak_point",
221
+ translation_key="electricity_produced_off_peak_point",
222
  native_unit_of_measurement=UnitOfPower.WATT,
223
  device_class=SensorDeviceClass.POWER,
224
  state_class=SensorStateClass.MEASUREMENT,
225
  ),
226
  PlugwiseSensorEntityDescription(
227
+ key="electricity_produced_peak_point",
228
+ translation_key="electricity_produced_peak_point",
229
  native_unit_of_measurement=UnitOfPower.WATT,
230
  device_class=SensorDeviceClass.POWER,
231
  state_class=SensorStateClass.MEASUREMENT,
232
  ),
233
  PlugwiseSensorEntityDescription(
234
+ key="electricity_produced_off_peak_cumulative",
235
+ translation_key="electricity_produced_off_peak_cumulative",
236
  native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
237
  device_class=SensorDeviceClass.ENERGY,
238
  state_class=SensorStateClass.TOTAL_INCREASING,
239
  ),
240
  PlugwiseSensorEntityDescription(
241
+ key="electricity_produced_peak_cumulative",
242
+ translation_key="electricity_produced_peak_cumulative",
243
  native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
244
  device_class=SensorDeviceClass.ENERGY,
245
  state_class=SensorStateClass.TOTAL_INCREASING,
246
  ),
247
  PlugwiseSensorEntityDescription(
248
+ key="electricity_phase_one_consumed",
249
+ translation_key="electricity_phase_one_consumed",
250
  device_class=SensorDeviceClass.POWER,
251
  native_unit_of_measurement=UnitOfPower.WATT,
252
  state_class=SensorStateClass.MEASUREMENT,
253
  ),
254
  PlugwiseSensorEntityDescription(
255
+ key="electricity_phase_two_consumed",
256
+ translation_key="electricity_phase_two_consumed",
257
  device_class=SensorDeviceClass.POWER,
258
  native_unit_of_measurement=UnitOfPower.WATT,
259
  state_class=SensorStateClass.MEASUREMENT,
260
  ),
261
  PlugwiseSensorEntityDescription(
262
+ key="electricity_phase_three_consumed",
263
+ translation_key="electricity_phase_three_consumed",
264
  device_class=SensorDeviceClass.POWER,
265
  native_unit_of_measurement=UnitOfPower.WATT,
266
  state_class=SensorStateClass.MEASUREMENT,
267
  ),
268
  PlugwiseSensorEntityDescription(
269
+ key="electricity_phase_one_produced",
270
+ translation_key="electricity_phase_one_produced",
271
  device_class=SensorDeviceClass.POWER,
272
  native_unit_of_measurement=UnitOfPower.WATT,
273
  state_class=SensorStateClass.MEASUREMENT,
274
  ),
275
  PlugwiseSensorEntityDescription(
276
+ key="electricity_phase_two_produced",
277
+ translation_key="electricity_phase_two_produced",
278
  device_class=SensorDeviceClass.POWER,
279
  native_unit_of_measurement=UnitOfPower.WATT,
280
  state_class=SensorStateClass.MEASUREMENT,
281
  ),
282
  PlugwiseSensorEntityDescription(
283
+ key="electricity_phase_three_produced",
284
+ translation_key="electricity_phase_three_produced",
285
  device_class=SensorDeviceClass.POWER,
286
  native_unit_of_measurement=UnitOfPower.WATT,
287
  state_class=SensorStateClass.MEASUREMENT,
288
  ),
289
  PlugwiseSensorEntityDescription(
290
+ key="voltage_phase_one",
291
+ translation_key="voltage_phase_one",
292
  device_class=SensorDeviceClass.VOLTAGE,
293
  native_unit_of_measurement=UnitOfElectricPotential.VOLT,
294
  state_class=SensorStateClass.MEASUREMENT,
295
  entity_registry_enabled_default=False,
296
  ),
297
  PlugwiseSensorEntityDescription(
298
+ key="voltage_phase_two",
299
+ translation_key="voltage_phase_two",
300
  device_class=SensorDeviceClass.VOLTAGE,
301
  native_unit_of_measurement=UnitOfElectricPotential.VOLT,
302
  state_class=SensorStateClass.MEASUREMENT,
303
  entity_registry_enabled_default=False,
304
  ),
305
  PlugwiseSensorEntityDescription(
306
+ key="voltage_phase_three",
307
+ translation_key="voltage_phase_three",
308
  device_class=SensorDeviceClass.VOLTAGE,
309
  native_unit_of_measurement=UnitOfElectricPotential.VOLT,
310
  state_class=SensorStateClass.MEASUREMENT,
311
  entity_registry_enabled_default=False,
312
  ),
313
  PlugwiseSensorEntityDescription(
314
+ key="gas_consumed_interval",
315
+ translation_key="gas_consumed_interval",
316
  native_unit_of_measurement=UnitOfVolumeFlowRate.CUBIC_METERS_PER_HOUR,
317
  state_class=SensorStateClass.MEASUREMENT,
318
  ),
319
  PlugwiseSensorEntityDescription(
320
+ key="gas_consumed_cumulative",
321
+ translation_key="gas_consumed_cumulative",
322
  native_unit_of_measurement=UnitOfVolume.CUBIC_METERS,
323
  device_class=SensorDeviceClass.GAS,
324
  state_class=SensorStateClass.TOTAL,
325
  ),
326
  PlugwiseSensorEntityDescription(
327
+ key="net_electricity_point",
328
+ translation_key="net_electricity_point",
329
  native_unit_of_measurement=UnitOfPower.WATT,
330
  device_class=SensorDeviceClass.POWER,
331
  state_class=SensorStateClass.MEASUREMENT,
332
  ),
333
  PlugwiseSensorEntityDescription(
334
+ key="net_electricity_cumulative",
335
+ translation_key="net_electricity_cumulative",
336
  native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
337
  device_class=SensorDeviceClass.ENERGY,
338
  state_class=SensorStateClass.TOTAL,
 
352
  state_class=SensorStateClass.MEASUREMENT,
353
  ),
354
  PlugwiseSensorEntityDescription(
355
+ key="modulation_level",
356
+ translation_key="modulation_level",
357
  native_unit_of_measurement=PERCENTAGE,
358
  entity_category=EntityCategory.DIAGNOSTIC,
359
  state_class=SensorStateClass.MEASUREMENT,
360
  ),
361
  PlugwiseSensorEntityDescription(
362
+ key="valve_position",
363
+ translation_key="valve_position",
364
  native_unit_of_measurement=PERCENTAGE,
365
  entity_category=EntityCategory.DIAGNOSTIC,
366
  state_class=SensorStateClass.MEASUREMENT,
367
  ),
368
  PlugwiseSensorEntityDescription(
369
+ key="water_pressure",
370
+ translation_key="water_pressure",
371
  native_unit_of_measurement=UnitOfPressure.BAR,
372
  device_class=SensorDeviceClass.PRESSURE,
373
  entity_category=EntityCategory.DIAGNOSTIC,
 
380
  state_class=SensorStateClass.MEASUREMENT,
381
  ),
382
  PlugwiseSensorEntityDescription(
383
+ key="dhw_temperature",
384
+ translation_key="dhw_temperature",
385
  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
386
  device_class=SensorDeviceClass.TEMPERATURE,
387
  entity_category=EntityCategory.DIAGNOSTIC,
388
  state_class=SensorStateClass.MEASUREMENT,
389
  ),
390
  PlugwiseSensorEntityDescription(
391
+ key="domestic_hot_water_setpoint",
392
+ translation_key="domestic_hot_water_setpoint",
393
  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
394
  device_class=SensorDeviceClass.TEMPERATURE,
395
  entity_category=EntityCategory.DIAGNOSTIC,
 
403
  entry: PlugwiseConfigEntry,
404
  async_add_entities: AddConfigEntryEntitiesCallback,
405
  ) -> None:
406
+ """Set up the Smile sensors from a config entry."""
 
407
  coordinator = entry.runtime_data
408
 
409
  @callback
 
412
  if not coordinator.new_devices:
413
  return
414
 
415
+ async_add_entities(
416
+ PlugwiseSensorEntity(coordinator, device_id, description)
417
+ for device_id in coordinator.new_devices
418
+ if (sensors := coordinator.data[device_id].get("sensors"))
419
+ for description in SENSORS
420
+ if description.key in sensors
421
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
422
 
423
  _add_entities()
424
  entry.async_on_unload(coordinator.async_add_listener(_add_entities))
 
437
  ) -> None:
438
  """Initialise the sensor."""
439
  super().__init__(coordinator, device_id)
 
440
  self._attr_unique_id = f"{device_id}-{description.key}"
441
+ self.entity_description = description
442
 
443
  @property
444
+ @override
445
+ def native_value(self) -> int | float:
446
  """Return the value reported by the sensor."""
447
+ 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,44 +1,46 @@
1
  {
2
  "config": {
3
  "abort": {
4
- "already_configured": "Device is already configured",
5
  "anna_with_adam": "Both Anna and Adam detected. Add your Adam instead of your Anna",
6
- "not_the_same_smile": "The provided Smile-ID does not match your device",
7
- "reconfigure_successful": "Reconfiguration was successful"
8
  },
9
  "error": {
10
- "cannot_connect": "Failed to connect",
11
- "invalid_auth": "Invalid authentication",
12
  "invalid_setup": "Add your Adam instead of your Anna, see the documentation",
13
- "response_error": "Invalid XML-data or error indication received",
14
- "unknown": "Unexpected error",
15
  "unsupported": "Device with unsupported firmware"
16
  },
17
  "step": {
18
  "reconfigure": {
19
  "data": {
20
- "host": "IP address"
 
21
  },
22
  "data_description": {
23
- "host": "The hostname or IP address of your Smile. You can find it in your router or the Plugwise app."
 
24
  },
25
  "description": "Update configuration for {title}."
26
  },
27
  "user": {
28
  "data": {
29
- "host": "IP address",
30
- "password": "Smile-ID",
31
- "port": "Port",
32
  "username": "Smile username"
33
  },
34
  "data_description": {
35
  "host": "The hostname or IP address of your Smile. You can find it in your router or the Plugwise app.",
36
- "password": "The Smile-ID printed on the label on the back of your Adam, P1, Smile-T, or Stretch.",
37
  "port": "By default your Smile uses port 80, normally you should not have to change this.",
38
  "username": "Default is `smile`, or `stretch` for the legacy Stretch."
39
  },
40
- "description": "Enter your Plugwise device data: (setup can take up to 90s)",
41
- "title": "Set up Plugwise Adam/Smile/Stretch"
42
  }
43
  }
44
  },
@@ -51,7 +53,7 @@
51
  "name": "Cooling enabled"
52
  },
53
  "cooling_state": {
54
- "name": "Cooling"
55
  },
56
  "dhw_state": {
57
  "name": "DHW state"
@@ -60,10 +62,7 @@
60
  "name": "Flame state"
61
  },
62
  "heating_state": {
63
- "name": "Heating"
64
- },
65
- "low_battery": {
66
- "name": "Battery status"
67
  },
68
  "plugwise_notification": {
69
  "name": "Plugwise notification"
@@ -80,14 +79,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,17 +112,17 @@
107
  "select_dhw_mode": {
108
  "name": "DHW mode",
109
  "state": {
110
- "auto": "Auto",
111
- "boost": "Boost",
112
- "comfort": "Comfort",
113
- "off": "Off"
114
  }
115
  },
116
  "select_gateway_mode": {
117
  "name": "Gateway mode",
118
  "state": {
119
  "away": "Pause",
120
- "full": "Normal",
121
  "vacation": "Vacation"
122
  }
123
  },
@@ -126,22 +131,22 @@
126
  "state": {
127
  "bleeding_cold": "Bleeding cold",
128
  "bleeding_hot": "Bleeding hot",
129
- "cooling": "Cooling",
130
- "heating": "Heating",
131
- "off": "Off"
132
  }
133
  },
134
  "select_schedule": {
135
  "name": "Thermostat schedule",
136
  "state": {
137
- "off": "Off"
138
  }
139
  },
140
  "select_zone_profile": {
141
  "name": "Zone profile",
142
  "state": {
143
- "active": "Active",
144
- "off": "Off",
145
  "passive": "Passive"
146
  }
147
  }
@@ -288,13 +293,13 @@
288
  },
289
  "switch": {
290
  "cooling_ena_switch": {
291
- "name": "Cooling"
292
  },
293
  "dhw_cm_switch": {
294
  "name": "DHW comfort mode"
295
  },
296
  "lock": {
297
- "name": "Lock"
298
  },
299
  "relay": {
300
  "name": "Relay"
@@ -303,50 +308,28 @@
303
  },
304
  "exceptions": {
305
  "authentication_failed": {
306
- "message": "Invalid authentication"
307
  },
308
  "data_incomplete_or_missing": {
309
- "message": "Data incomplete or missing"
310
  },
311
  "error_communicating_with_api": {
312
- "message": "Error communicating with API: {error}"
313
  },
314
  "failed_to_connect": {
315
- "message": "Failed to connect"
316
  },
317
  "invalid_setup": {
318
  "message": "Add your Adam instead of your Anna, see the documentation"
319
  },
320
  "response_error": {
321
- "message": "Invalid XML-data or error indication received"
322
  },
323
  "set_schedule_first": {
324
- "message": "Failed setting HVACMode, set a schedule first"
325
  },
326
  "unsupported_firmware": {
327
- "message": "Device with unsupported firmware"
328
- }
329
- },
330
- "options": {
331
- "step": {
332
- "init": {
333
- "data": {
334
- "cooling_on": "Anna: cooling-mode is on",
335
- "refresh_interval": "Frontend refresh-time (1.5 - 5 seconds) *) beta-only option",
336
- "scan_interval": "Scan interval (seconds) *) beta-only option"
337
- },
338
- "description": "Adjust Adam/Smile/Stretch options"
339
- },
340
- "none": {
341
- "description": "This integration does not provide any options",
342
- "title": "No options available"
343
- }
344
- }
345
- },
346
- "services": {
347
- "delete_notification": {
348
- "description": "Deletes a Plugwise notification",
349
- "name": "Delete Plugwise notification"
350
  }
351
  }
352
  }
 
1
  {
2
  "config": {
3
  "abort": {
4
+ "already_configured": "[%key:common::config_flow::abort::already_configured_service%]",
5
  "anna_with_adam": "Both Anna and Adam detected. Add your Adam instead of your Anna",
6
+ "not_the_same_smile": "The configured Smile ID does not match the Smile ID on the requested IP address.",
7
+ "reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]"
8
  },
9
  "error": {
10
+ "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
11
+ "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
12
  "invalid_setup": "Add your Adam instead of your Anna, see the documentation",
13
+ "response_error": "Invalid XML data, or error indication received",
14
+ "unknown": "[%key:common::config_flow::error::unknown%]",
15
  "unsupported": "Device with unsupported firmware"
16
  },
17
  "step": {
18
  "reconfigure": {
19
  "data": {
20
+ "host": "[%key:common::config_flow::data::ip%]",
21
+ "port": "[%key:common::config_flow::data::port%]"
22
  },
23
  "data_description": {
24
+ "host": "[%key:component::plugwise::config::step::user::data_description::host%]",
25
+ "port": "[%key:component::plugwise::config::step::user::data_description::port%]"
26
  },
27
  "description": "Update configuration for {title}."
28
  },
29
  "user": {
30
  "data": {
31
+ "host": "[%key:common::config_flow::data::ip%]",
32
+ "password": "Smile ID",
33
+ "port": "[%key:common::config_flow::data::port%]",
34
  "username": "Smile username"
35
  },
36
  "data_description": {
37
  "host": "The hostname or IP address of your Smile. You can find it in your router or the Plugwise app.",
38
+ "password": "The Smile ID printed on the label on the back of your Adam, Smile-T, or P1.",
39
  "port": "By default your Smile uses port 80, normally you should not have to change this.",
40
  "username": "Default is `smile`, or `stretch` for the legacy Stretch."
41
  },
42
+ "description": "Please enter",
43
+ "title": "Connect to the Smile"
44
  }
45
  }
46
  },
 
53
  "name": "Cooling enabled"
54
  },
55
  "cooling_state": {
56
+ "name": "[%key:component::climate::entity_component::_::state_attributes::hvac_action::state::cooling%]"
57
  },
58
  "dhw_state": {
59
  "name": "DHW state"
 
62
  "name": "Flame state"
63
  },
64
  "heating_state": {
65
+ "name": "[%key:component::climate::entity_component::_::state_attributes::hvac_action::state::heating%]"
 
 
 
66
  },
67
  "plugwise_notification": {
68
  "name": "Plugwise notification"
 
79
  "climate": {
80
  "plugwise": {
81
  "state_attributes": {
82
+ "available_schemas": {
83
+ "name": "Available schemas"
84
+ },
85
  "preset_mode": {
86
  "state": {
87
  "asleep": "Night",
88
+ "away": "[%key:common::state::not_home%]",
89
+ "home": "[%key:common::state::home%]",
90
  "no_frost": "Anti-frost",
91
  "vacation": "Vacation"
92
  }
93
+ },
94
+ "selected_schema": {
95
+ "name": "Selected schema"
96
  }
97
  }
98
  }
 
112
  "select_dhw_mode": {
113
  "name": "DHW mode",
114
  "state": {
115
+ "auto": "[%key:common::state::auto%]",
116
+ "boost": "[%key:component::climate::entity_component::_::state_attributes::preset_mode::state::boost%]",
117
+ "comfort": "[%key:component::climate::entity_component::_::state_attributes::preset_mode::state::comfort%]",
118
+ "off": "[%key:common::state::off%]"
119
  }
120
  },
121
  "select_gateway_mode": {
122
  "name": "Gateway mode",
123
  "state": {
124
  "away": "Pause",
125
+ "full": "[%key:common::state::normal%]",
126
  "vacation": "Vacation"
127
  }
128
  },
 
131
  "state": {
132
  "bleeding_cold": "Bleeding cold",
133
  "bleeding_hot": "Bleeding hot",
134
+ "cooling": "[%key:component::climate::entity_component::_::state_attributes::hvac_action::state::cooling%]",
135
+ "heating": "[%key:component::climate::entity_component::_::state_attributes::hvac_action::state::heating%]",
136
+ "off": "[%key:common::state::off%]"
137
  }
138
  },
139
  "select_schedule": {
140
  "name": "Thermostat schedule",
141
  "state": {
142
+ "off": "[%key:common::state::off%]"
143
  }
144
  },
145
  "select_zone_profile": {
146
  "name": "Zone profile",
147
  "state": {
148
+ "active": "[%key:common::state::active%]",
149
+ "off": "[%key:common::state::off%]",
150
  "passive": "Passive"
151
  }
152
  }
 
293
  },
294
  "switch": {
295
  "cooling_ena_switch": {
296
+ "name": "[%key:component::climate::entity_component::_::state_attributes::hvac_action::state::cooling%]"
297
  },
298
  "dhw_cm_switch": {
299
  "name": "DHW comfort mode"
300
  },
301
  "lock": {
302
+ "name": "[%key:component::lock::title%]"
303
  },
304
  "relay": {
305
  "name": "Relay"
 
308
  },
309
  "exceptions": {
310
  "authentication_failed": {
311
+ "message": "[%key:common::config_flow::error::invalid_auth%]"
312
  },
313
  "data_incomplete_or_missing": {
314
+ "message": "Data incomplete or missing."
315
  },
316
  "error_communicating_with_api": {
317
+ "message": "Error communicating with API: {error}."
318
  },
319
  "failed_to_connect": {
320
+ "message": "[%key:common::config_flow::error::cannot_connect%]"
321
  },
322
  "invalid_setup": {
323
  "message": "Add your Adam instead of your Anna, see the documentation"
324
  },
325
  "response_error": {
326
+ "message": "[%key:component::plugwise::config::error::response_error%]"
327
  },
328
  "set_schedule_first": {
329
+ "message": "Failed setting HVACMode, set a schedule first."
330
  },
331
  "unsupported_firmware": {
332
+ "message": "[%key:component::plugwise::config::error::unsupported%]"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
333
  }
334
  }
335
  }
/home/runner/work/progress/progress/clones/beta/{beta/custom_components → ha-core/homeassistant/components}/plugwise/switch.py RENAMED
@@ -1,7 +1,7 @@
1
  """Plugwise Switch component for HomeAssistant."""
2
 
3
  from dataclasses import dataclass
4
- from typing import Any
5
 
6
  from plugwise.constants import SwitchType
7
 
@@ -14,17 +14,6 @@
14
  from homeassistant.core import HomeAssistant, callback
15
  from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
16
 
17
- from .const import (
18
- COOLING_ENA_SWITCH,
19
- DHW_CM_SWITCH,
20
- LOCK,
21
- LOGGER, # pw-beta
22
- MEMBERS,
23
- RELAY,
24
- SWITCHES,
25
- )
26
-
27
- # Upstream consts
28
  from .coordinator import PlugwiseConfigEntry, PlugwiseDataUpdateCoordinator
29
  from .entity import PlugwiseEntity
30
  from .util import plugwise_command
@@ -39,29 +28,25 @@
39
  key: SwitchType
40
 
41
 
42
- # Upstream consts
43
- PLUGWISE_SWITCHES: tuple[PlugwiseSwitchEntityDescription, ...] = (
44
  PlugwiseSwitchEntityDescription(
45
- key=DHW_CM_SWITCH,
46
- translation_key=DHW_CM_SWITCH,
47
- device_class=SwitchDeviceClass.SWITCH,
48
  entity_category=EntityCategory.CONFIG,
49
  ),
50
  PlugwiseSwitchEntityDescription(
51
- key=LOCK,
52
- translation_key=LOCK,
53
- device_class=SwitchDeviceClass.SWITCH,
54
  entity_category=EntityCategory.CONFIG,
55
  ),
56
  PlugwiseSwitchEntityDescription(
57
- key=RELAY,
58
- translation_key=RELAY,
59
  device_class=SwitchDeviceClass.SWITCH,
60
  ),
61
  PlugwiseSwitchEntityDescription(
62
- key=COOLING_ENA_SWITCH,
63
- translation_key=COOLING_ENA_SWITCH,
64
- device_class=SwitchDeviceClass.SWITCH,
65
  entity_category=EntityCategory.CONFIG,
66
  ),
67
  )
@@ -72,7 +57,7 @@
72
  entry: PlugwiseConfigEntry,
73
  async_add_entities: AddConfigEntryEntitiesCallback,
74
  ) -> None:
75
- """Set up Plugwise switches from a config entry."""
76
  coordinator = entry.runtime_data
77
 
78
  @callback
@@ -81,28 +66,13 @@
81
  if not coordinator.new_devices:
82
  return
83
 
84
- # Upstream consts
85
- # async_add_entities(
86
- # PlugwiseSwitchEntity(coordinator, device_id, description)
87
- # for device_id in coordinator.new_devices
88
- # if (switches := coordinator.data.devices[device_id].get(SWITCHES))
89
- # for description in PLUGWISE_SWITCHES
90
- # if description.key in switches
91
- # )
92
- # pw-beta alternative for debugging
93
- entities: list[PlugwiseSwitchEntity] = []
94
- for device_id in coordinator.new_devices:
95
- device = coordinator.data[device_id]
96
- if not (switches := device.get(SWITCHES)):
97
- continue
98
- for description in PLUGWISE_SWITCHES:
99
- if description.key not in switches:
100
- continue
101
- entities.append(PlugwiseSwitchEntity(coordinator, device_id, description))
102
- LOGGER.debug(
103
- "Add %s %s switch", device["name"], description.translation_key
104
- )
105
- async_add_entities(entities)
106
 
107
  _add_entities()
108
  entry.async_on_unload(coordinator.async_add_listener(_add_entities))
@@ -121,30 +91,33 @@
121
  ) -> None:
122
  """Set up the Plugwise API."""
123
  super().__init__(coordinator, device_id)
124
- self.entity_description = description
125
  self._attr_unique_id = f"{device_id}-{description.key}"
 
126
 
127
  @property
128
- def is_on(self) -> bool | None:
 
129
  """Return True if entity is on."""
130
- return self.device.get(SWITCHES, {}).get(self.entity_description.key) # Upstream const
131
 
132
  @plugwise_command
 
133
  async def async_turn_on(self, **kwargs: Any) -> None:
134
  """Turn the device on."""
135
  await self.coordinator.api.set_switch_state(
136
  self._dev_id,
137
- self.device.get(MEMBERS),
138
  self.entity_description.key,
139
  "on",
140
- ) # Upstream const
141
 
142
  @plugwise_command
 
143
  async def async_turn_off(self, **kwargs: Any) -> None:
144
  """Turn the device off."""
145
  await self.coordinator.api.set_switch_state(
146
  self._dev_id,
147
- self.device.get(MEMBERS),
148
  self.entity_description.key,
149
  "off",
150
- ) # Upstream const
 
1
  """Plugwise Switch component for HomeAssistant."""
2
 
3
  from dataclasses import dataclass
4
+ from typing import Any, override
5
 
6
  from plugwise.constants import SwitchType
7
 
 
14
  from homeassistant.core import HomeAssistant, callback
15
  from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
16
 
 
 
 
 
 
 
 
 
 
 
 
17
  from .coordinator import PlugwiseConfigEntry, PlugwiseDataUpdateCoordinator
18
  from .entity import PlugwiseEntity
19
  from .util import plugwise_command
 
28
  key: SwitchType
29
 
30
 
31
+ SWITCHES: tuple[PlugwiseSwitchEntityDescription, ...] = (
 
32
  PlugwiseSwitchEntityDescription(
33
+ key="dhw_cm_switch",
34
+ translation_key="dhw_cm_switch",
 
35
  entity_category=EntityCategory.CONFIG,
36
  ),
37
  PlugwiseSwitchEntityDescription(
38
+ key="lock",
39
+ translation_key="lock",
 
40
  entity_category=EntityCategory.CONFIG,
41
  ),
42
  PlugwiseSwitchEntityDescription(
43
+ key="relay",
44
+ translation_key="relay",
45
  device_class=SwitchDeviceClass.SWITCH,
46
  ),
47
  PlugwiseSwitchEntityDescription(
48
+ key="cooling_ena_switch",
49
+ translation_key="cooling_ena_switch",
 
50
  entity_category=EntityCategory.CONFIG,
51
  ),
52
  )
 
57
  entry: PlugwiseConfigEntry,
58
  async_add_entities: AddConfigEntryEntitiesCallback,
59
  ) -> None:
60
+ """Set up the Smile switches from a config entry."""
61
  coordinator = entry.runtime_data
62
 
63
  @callback
 
66
  if not coordinator.new_devices:
67
  return
68
 
69
+ async_add_entities(
70
+ PlugwiseSwitchEntity(coordinator, device_id, description)
71
+ for device_id in coordinator.new_devices
72
+ if (switches := coordinator.data[device_id].get("switches"))
73
+ for description in SWITCHES
74
+ if description.key in switches
75
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
 
77
  _add_entities()
78
  entry.async_on_unload(coordinator.async_add_listener(_add_entities))
 
91
  ) -> None:
92
  """Set up the Plugwise API."""
93
  super().__init__(coordinator, device_id)
 
94
  self._attr_unique_id = f"{device_id}-{description.key}"
95
+ self.entity_description = description
96
 
97
  @property
98
+ @override
99
+ def is_on(self) -> bool:
100
  """Return True if entity is on."""
101
+ return self.device["switches"][self.entity_description.key]
102
 
103
  @plugwise_command
104
+ @override
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
+ )
113
 
114
  @plugwise_command
115
+ @override
116
  async def async_turn_off(self, **kwargs: Any) -> None:
117
  """Turn the device off."""
118
  await self.coordinator.api.set_switch_state(
119
  self._dev_id,
120
+ self.device.get("members"),
121
  self.entity_description.key,
122
  "off",
123
+ )
/home/runner/work/progress/progress/clones/beta/{beta/custom_components → ha-core/homeassistant/components}/plugwise/util.py RENAMED
@@ -10,15 +10,10 @@
10
  from .const import DOMAIN
11
  from .entity import PlugwiseEntity
12
 
13
- # For reference:
14
- # PlugwiseEntityT = TypeVar("PlugwiseEntityT", bound=PlugwiseEntity)
15
- # R = TypeVar("_R")
16
- # P = ParamSpec("_P")
17
 
18
-
19
- def plugwise_command[PlugwiseEntityT: PlugwiseEntity, **P, R](
20
- func: Callable[Concatenate[PlugwiseEntityT, P], Awaitable[R]],
21
- ) -> Callable[Concatenate[PlugwiseEntityT, P], Coroutine[Any, Any, R]]:
22
  """Decorate Plugwise calls that send commands/make changes to the device.
23
 
24
  A decorator that wraps the passed in function, catches Plugwise errors,
@@ -26,8 +21,8 @@
26
  """
27
 
28
  async def handler(
29
- self: PlugwiseEntityT, *args: P.args, **kwargs: P.kwargs
30
- ) -> R:
31
  try:
32
  return await func(self, *args, **kwargs)
33
  except PlugwiseException as err:
 
10
  from .const import DOMAIN
11
  from .entity import PlugwiseEntity
12
 
 
 
 
 
13
 
14
+ def plugwise_command[_PlugwiseEntityT: PlugwiseEntity, **_P, _R](
15
+ func: Callable[Concatenate[_PlugwiseEntityT, _P], Awaitable[_R]],
16
+ ) -> Callable[Concatenate[_PlugwiseEntityT, _P], Coroutine[Any, Any, _R]]:
 
17
  """Decorate Plugwise calls that send commands/make changes to the device.
18
 
19
  A decorator that wraps the passed in function, catches Plugwise errors,
 
21
  """
22
 
23
  async def handler(
24
+ self: _PlugwiseEntityT, *args: _P.args, **kwargs: _P.kwargs
25
+ ) -> _R:
26
  try:
27
  return await func(self, *args, **kwargs)
28
  except PlugwiseException as err: