Diff to HTML by rtfpessoa

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