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