plugwise 1.5.2__py3-none-any.whl → 1.6.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
plugwise/__init__.py CHANGED
@@ -69,6 +69,7 @@ class Smile(SmileComm):
69
69
  self._elga = False
70
70
  self._is_thermostat = False
71
71
  self._last_active: dict[str, str | None] = {}
72
+ self._loc_data: dict[str, ThermoLoc] = {}
72
73
  self._on_off_device = False
73
74
  self._opentherm_device = False
74
75
  self._schedule_old_states: dict[str, dict[str, str]] = {}
@@ -76,7 +77,6 @@ class Smile(SmileComm):
76
77
  self._stretch_v2 = False
77
78
  self._target_smile: str = NONE
78
79
  self.gateway_id: str = NONE
79
- self.loc_data: dict[str, ThermoLoc] = {}
80
80
  self.smile_fw_version: Version | None = None
81
81
  self.smile_hostname: str = NONE
82
82
  self.smile_hw_version: str | None = None
@@ -134,11 +134,11 @@ class Smile(SmileComm):
134
134
  self._elga,
135
135
  self._is_thermostat,
136
136
  self._last_active,
137
+ self._loc_data,
137
138
  self._on_off_device,
138
139
  self._opentherm_device,
139
140
  self._schedule_old_states,
140
141
  self.gateway_id,
141
- self.loc_data,
142
142
  self.smile_fw_version,
143
143
  self.smile_hostname,
144
144
  self.smile_hw_version,
@@ -155,11 +155,11 @@ class Smile(SmileComm):
155
155
  self._request,
156
156
  self._websession,
157
157
  self._is_thermostat,
158
+ self._loc_data,
158
159
  self._on_off_device,
159
160
  self._opentherm_device,
160
161
  self._stretch_v2,
161
162
  self._target_smile,
162
- self.loc_data,
163
163
  self.smile_fw_version,
164
164
  self.smile_hostname,
165
165
  self.smile_hw_version,
@@ -173,7 +173,7 @@ class Smile(SmileComm):
173
173
  )
174
174
 
175
175
  # Update all endpoints on first connect
176
- await self._smile_api.full_update_device()
176
+ await self._smile_api.full_xml_update()
177
177
 
178
178
  return self.smile_version
179
179
 
@@ -298,17 +298,17 @@ class Smile(SmileComm):
298
298
  self.smile_legacy = True
299
299
  return return_model
300
300
 
301
- async def full_update_device(self) -> None:
301
+ async def full_xml_update(self) -> None:
302
302
  """Helper-function used for testing."""
303
- await self._smile_api.full_update_device()
303
+ await self._smile_api.full_xml_update()
304
304
 
305
- def get_all_devices(self) -> None:
305
+ def get_all_gateway_entities(self) -> None:
306
306
  """Helper-function used for testing."""
307
- self._smile_api.get_all_devices()
307
+ self._smile_api.get_all_gateway_entities()
308
308
 
309
309
  async def async_update(self) -> PlugwiseData:
310
- """Perform an incremental update for updating the various device states."""
311
- data = PlugwiseData({}, {})
310
+ """Update the various entities and their states."""
311
+ data = PlugwiseData(devices={}, gateway={})
312
312
  try:
313
313
  data = await self._smile_api.async_update()
314
314
  self.gateway_id = data.gateway["gateway_id"]
plugwise/common.py CHANGED
@@ -11,8 +11,8 @@ from plugwise.constants import (
11
11
  SPECIAL_PLUG_TYPES,
12
12
  SWITCH_GROUP_TYPES,
13
13
  ApplianceType,
14
- DeviceData,
15
- ModelData,
14
+ GwEntityData,
15
+ ModuleData,
16
16
  SensorType,
17
17
  )
18
18
  from plugwise.util import (
@@ -40,7 +40,7 @@ class SmileCommon:
40
40
  self._heater_id: str
41
41
  self._on_off_device: bool
42
42
  self._opentherm_device: bool
43
- self.gw_devices: dict[str, DeviceData]
43
+ self.gw_entities: dict[str, GwEntityData]
44
44
  self.smile_name: str
45
45
  self.smile_type: str
46
46
 
@@ -108,7 +108,7 @@ class SmileCommon:
108
108
 
109
109
  return appl
110
110
 
111
- def _collect_power_values(self, data: DeviceData, loc: Munch, tariff: str, legacy: bool = False) -> None:
111
+ def _collect_power_values(self, data: GwEntityData, loc: Munch, tariff: str, legacy: bool = False) -> None:
112
112
  """Something."""
113
113
  for loc.peak_select in ("nl_peak", "nl_offpeak"):
114
114
  loc.locator = (
@@ -131,6 +131,19 @@ class SmileCommon:
131
131
  key = cast(SensorType, loc.key_string)
132
132
  data["sensors"][key] = loc.f_val
133
133
 
134
+ def _count_data_items(self, data: GwEntityData) -> None:
135
+ """When present, count the binary_sensors, sensors and switches dict-items, don't count the dicts.
136
+
137
+ Also, count the remaining single data items, the amount of dicts present have already been pre-subtracted in the previous step.
138
+ """
139
+ if "binary_sensors" in data:
140
+ self._count += len(data["binary_sensors"]) - 1
141
+ if "sensors" in data:
142
+ self._count += len(data["sensors"]) - 1
143
+ if "switches" in data:
144
+ self._count += len(data["switches"]) - 1
145
+ self._count += len(data)
146
+
134
147
  def _power_data_peak_value(self, loc: Munch, legacy: bool) -> Munch:
135
148
  """Helper-function for _power_data_from_location() and _power_data_from_modules()."""
136
149
  loc.found = True
@@ -160,8 +173,8 @@ class SmileCommon:
160
173
  measurement: str,
161
174
  net_string: SensorType,
162
175
  f_val: float | int,
163
- direct_data: DeviceData,
164
- ) -> DeviceData:
176
+ data: GwEntityData,
177
+ ) -> GwEntityData:
165
178
  """Calculate differential energy."""
166
179
  if (
167
180
  "electricity" in measurement
@@ -171,10 +184,10 @@ class SmileCommon:
171
184
  diff = 1
172
185
  if "produced" in measurement:
173
186
  diff = -1
174
- if net_string not in direct_data["sensors"]:
187
+ if net_string not in data["sensors"]:
175
188
  tmp_val: float | int = 0
176
189
  else:
177
- tmp_val = direct_data["sensors"][net_string]
190
+ tmp_val = data["sensors"][net_string]
178
191
 
179
192
  if isinstance(f_val, int):
180
193
  tmp_val += f_val * diff
@@ -182,13 +195,13 @@ class SmileCommon:
182
195
  tmp_val += float(f_val * diff)
183
196
  tmp_val = float(f"{round(tmp_val, 3):.3f}")
184
197
 
185
- direct_data["sensors"][net_string] = tmp_val
198
+ data["sensors"][net_string] = tmp_val
186
199
 
187
- return direct_data
200
+ return data
188
201
 
189
- def _create_gw_devices(self, appl: Munch) -> None:
190
- """Helper-function for creating/updating gw_devices."""
191
- self.gw_devices[appl.dev_id] = {"dev_class": appl.pwclass}
202
+ def _create_gw_entities(self, appl: Munch) -> None:
203
+ """Helper-function for creating/updating gw_entities."""
204
+ self.gw_entities[appl.entity_id] = {"dev_class": appl.pwclass}
192
205
  self._count += 1
193
206
  for key, value in {
194
207
  "available": appl.available,
@@ -204,30 +217,30 @@ class SmileCommon:
204
217
  }.items():
205
218
  if value is not None or key == "location":
206
219
  appl_key = cast(ApplianceType, key)
207
- self.gw_devices[appl.dev_id][appl_key] = value
220
+ self.gw_entities[appl.entity_id][appl_key] = value
208
221
  self._count += 1
209
222
 
210
- def _device_data_switching_group(
211
- self, device: DeviceData, data: DeviceData
223
+ def _entity_switching_group(
224
+ self, entity: GwEntityData, data: GwEntityData
212
225
  ) -> None:
213
- """Helper-function for _get_device_data().
226
+ """Helper-function for _get_device_zone_data().
214
227
 
215
228
  Determine switching group device data.
216
229
  """
217
- if device["dev_class"] in SWITCH_GROUP_TYPES:
230
+ if entity["dev_class"] in SWITCH_GROUP_TYPES:
218
231
  counter = 0
219
- for member in device["members"]:
220
- if self.gw_devices[member]["switches"].get("relay"):
232
+ for member in entity["members"]:
233
+ if self.gw_entities[member]["switches"].get("relay"):
221
234
  counter += 1
222
235
  data["switches"]["relay"] = counter != 0
223
236
  self._count += 1
224
237
 
225
- def _get_group_switches(self) -> dict[str, DeviceData]:
226
- """Helper-function for smile.py: get_all_devices().
238
+ def _get_group_switches(self) -> dict[str, GwEntityData]:
239
+ """Helper-function for smile.py: get_all_gateway_entities().
227
240
 
228
241
  Collect switching- or pump-group info.
229
242
  """
230
- switch_groups: dict[str, DeviceData] = {}
243
+ switch_groups: dict[str, GwEntityData] = {}
231
244
  # P1 and Anna don't have switchgroups
232
245
  if self.smile_type == "power" or self.smile(ANNA):
233
246
  return switch_groups
@@ -240,25 +253,22 @@ class SmileCommon:
240
253
  group_appliances = group.findall("appliances/appliance")
241
254
  for item in group_appliances:
242
255
  # Check if members are not orphaned - stretch
243
- if item.attrib["id"] in self.gw_devices:
256
+ if item.attrib["id"] in self.gw_entities:
244
257
  members.append(item.attrib["id"])
245
258
 
246
259
  if group_type in SWITCH_GROUP_TYPES and members:
247
- switch_groups.update(
248
- {
249
- group_id: {
250
- "dev_class": group_type,
251
- "model": "Switchgroup",
252
- "name": group_name,
253
- "members": members,
254
- },
255
- },
256
- )
260
+ switch_groups[group_id] = {
261
+ "dev_class": group_type,
262
+ "model": "Switchgroup",
263
+ "name": group_name,
264
+ "members": members,
265
+ "vendor": "Plugwise",
266
+ }
257
267
  self._count += 4
258
268
 
259
269
  return switch_groups
260
270
 
261
- def _get_lock_state(self, xml: etree, data: DeviceData, stretch_v2: bool = False) -> None:
271
+ def _get_lock_state(self, xml: etree, data: GwEntityData, stretch_v2: bool = False) -> None:
262
272
  """Helper-function for _get_measurement_data().
263
273
 
264
274
  Adam & Stretches: obtain the relay-switch lock state.
@@ -280,12 +290,12 @@ class SmileCommon:
280
290
  locator: str,
281
291
  xml_2: etree = None,
282
292
  legacy: bool = False,
283
- ) -> ModelData:
293
+ ) -> ModuleData:
284
294
  """Helper-function for _energy_device_info_finder() and _appliance_info_finder().
285
295
 
286
296
  Collect requested info from MODULES.
287
297
  """
288
- model_data: ModelData = {
298
+ module_data: ModuleData = {
289
299
  "contents": False,
290
300
  "firmware_version": None,
291
301
  "hardware_version": None,
@@ -304,25 +314,25 @@ class SmileCommon:
304
314
  search = return_valid(xml_2, self._domain_objects)
305
315
  module = search.find(loc)
306
316
  if module is not None: # pylint: disable=consider-using-assignment-expr
307
- model_data["contents"] = True
308
- get_vendor_name(module, model_data)
309
- model_data["vendor_model"] = module.find("vendor_model").text
310
- model_data["hardware_version"] = module.find("hardware_version").text
311
- model_data["firmware_version"] = module.find("firmware_version").text
312
- self._get_zigbee_data(module, model_data, legacy)
317
+ module_data["contents"] = True
318
+ get_vendor_name(module, module_data)
319
+ module_data["vendor_model"] = module.find("vendor_model").text
320
+ module_data["hardware_version"] = module.find("hardware_version").text
321
+ module_data["firmware_version"] = module.find("firmware_version").text
322
+ self._get_zigbee_data(module, module_data, legacy)
313
323
 
314
- return model_data
324
+ return module_data
315
325
 
316
- def _get_zigbee_data(self, module: etree, model_data: ModelData, legacy: bool) -> None:
317
- """Helper-function for _get_model_data()."""
326
+ def _get_zigbee_data(self, module: etree, module_data: ModuleData, legacy: bool) -> None:
327
+ """Helper-function for _get_module_data()."""
318
328
  if legacy:
319
329
  # Stretches
320
330
  if (router := module.find("./protocols/network_router")) is not None:
321
- model_data["zigbee_mac_address"] = router.find("mac_address").text
331
+ module_data["zigbee_mac_address"] = router.find("mac_address").text
322
332
  # Also look for the Circle+/Stealth M+
323
333
  if (coord := module.find("./protocols/network_coordinator")) is not None:
324
- model_data["zigbee_mac_address"] = coord.find("mac_address").text
334
+ module_data["zigbee_mac_address"] = coord.find("mac_address").text
325
335
  # Adam
326
336
  elif (zb_node := module.find("./protocols/zig_bee_node")) is not None:
327
- model_data["zigbee_mac_address"] = zb_node.find("mac_address").text
328
- model_data["reachable"] = zb_node.find("reachable").text == "true"
337
+ module_data["zigbee_mac_address"] = zb_node.find("mac_address").text
338
+ module_data["reachable"] = zb_node.find("reachable").text == "true"
plugwise/constants.py CHANGED
@@ -190,6 +190,14 @@ OBSOLETE_MEASUREMENTS: Final[tuple[str, ...]] = (
190
190
  "outdoor_temperature",
191
191
  )
192
192
 
193
+ # Zone/climate related measurements
194
+ ZONE_MEASUREMENTS: Final[dict[str, DATA | UOM]] = {
195
+ "electricity_consumed": UOM(POWER_WATT),
196
+ "electricity_produced": UOM(POWER_WATT),
197
+ "relay": UOM(NONE),
198
+ "temperature": UOM(TEMP_CELSIUS), # HA Core thermostat current_temperature
199
+ }
200
+
193
201
  # Literals
194
202
  SMILE_P1 = "Smile P1"
195
203
  POWER = "power"
@@ -397,8 +405,8 @@ class GatewayData(TypedDict, total=False):
397
405
  smile_name: str
398
406
 
399
407
 
400
- class ModelData(TypedDict):
401
- """The ModelData class."""
408
+ class ModuleData(TypedDict):
409
+ """The Module data class."""
402
410
 
403
411
  contents: bool
404
412
  firmware_version: str | None
@@ -492,9 +500,9 @@ class ThermoLoc(TypedDict, total=False):
492
500
  """Thermo Location class."""
493
501
 
494
502
  name: str
495
- primary: str | None
503
+ primary: list[str]
496
504
  primary_prio: int
497
- secondary: set[str]
505
+ secondary: list[str]
498
506
 
499
507
 
500
508
  class ActuatorData(TypedDict, total=False):
@@ -508,8 +516,11 @@ class ActuatorData(TypedDict, total=False):
508
516
  upper_bound: float
509
517
 
510
518
 
511
- class DeviceData(TypedDict, total=False):
512
- """The Device Data class, covering the collected and ordered output-data per device."""
519
+ class GwEntityData(TypedDict, total=False):
520
+ """The Gateway Entity data class.
521
+
522
+ Covering the collected output-data per device or location.
523
+ """
513
524
 
514
525
  # Appliance base data
515
526
  dev_class: str
@@ -544,13 +555,13 @@ class DeviceData(TypedDict, total=False):
544
555
  select_gateway_mode: str
545
556
  select_regulation_mode: str
546
557
 
547
- # Master Thermostats
558
+ # Thermostat-related
559
+ thermostats: dict[str, list[str]]
548
560
  # Presets:
549
561
  active_preset: str | None
550
562
  preset_modes: list[str] | None
551
563
  # Schedules:
552
564
  available_schedules: list[str]
553
- last_used: str | None
554
565
  select_schedule: str
555
566
 
556
567
  climate_mode: str
@@ -571,5 +582,6 @@ class DeviceData(TypedDict, total=False):
571
582
  class PlugwiseData:
572
583
  """Plugwise data provided as output."""
573
584
 
585
+ devices: dict[str, GwEntityData]
574
586
  gateway: GatewayData
575
- devices: dict[str, DeviceData]
587
+
plugwise/data.py CHANGED
@@ -13,9 +13,8 @@ from plugwise.constants import (
13
13
  MIN_SETPOINT,
14
14
  NONE,
15
15
  OFF,
16
- ZONE_THERMOSTATS,
17
16
  ActuatorData,
18
- DeviceData,
17
+ GwEntityData,
19
18
  )
20
19
  from plugwise.helper import SmileHelper
21
20
  from plugwise.util import remove_empty_platform_dicts
@@ -29,12 +28,16 @@ class SmileData(SmileHelper):
29
28
  SmileHelper.__init__(self)
30
29
 
31
30
 
32
- def _all_device_data(self) -> None:
33
- """Helper-function for get_all_devices().
31
+ def _all_entity_data(self) -> None:
32
+ """Helper-function for get_all_gateway_entities().
34
33
 
35
- Collect data for each device and add to self.gw_data and self.gw_devices.
34
+ Collect data for each entity and add to self.gw_data and self.gw_entities.
36
35
  """
37
- self._update_gw_devices()
36
+ self._update_gw_entities()
37
+ if self.smile(ADAM):
38
+ self._update_zones()
39
+ self.gw_entities.update(self._zones)
40
+
38
41
  self.gw_data.update(
39
42
  {
40
43
  "gateway_id": self.gateway_id,
@@ -49,31 +52,40 @@ class SmileData(SmileHelper):
49
52
  {"heater_id": self._heater_id, "cooling_present": self._cooling_present}
50
53
  )
51
54
 
52
- def _update_gw_devices(self) -> None:
53
- """Helper-function for _all_device_data() and async_update().
55
+ def _update_zones(self) -> None:
56
+ """Helper-function for _all_entity_data() and async_update().
57
+
58
+ Collect data for each zone/location and add to self._zones.
59
+ """
60
+ for location_id, zone in self._zones.items():
61
+ data = self._get_location_data(location_id)
62
+ zone.update(data)
63
+
64
+ def _update_gw_entities(self) -> None:
65
+ """Helper-function for _all_entities_data() and async_update().
54
66
 
55
- Collect data for each device and add to self.gw_devices.
67
+ Collect data for each entity and add to self.gw_entities.
56
68
  """
57
69
  mac_list: list[str] = []
58
- for device_id, device in self.gw_devices.items():
59
- data = self._get_device_data(device_id)
60
- if device_id == self.gateway_id:
70
+ for entity_id, entity in self.gw_entities.items():
71
+ data = self._get_entity_data(entity_id)
72
+ if entity_id == self.gateway_id:
61
73
  mac_list = self._detect_low_batteries()
62
- self._add_or_update_notifications(device_id, device, data)
74
+ self._add_or_update_notifications(entity_id, entity, data)
63
75
 
64
- device.update(data)
76
+ entity.update(data)
65
77
  is_battery_low = (
66
78
  mac_list
67
- and "low_battery" in device["binary_sensors"]
68
- and device["zigbee_mac_address"] in mac_list
69
- and device["dev_class"] in ("thermo_sensor", "thermostatic_radiator_valve", "zone_thermometer", "zone_thermostat")
79
+ and "low_battery" in entity["binary_sensors"]
80
+ and entity["zigbee_mac_address"] in mac_list
81
+ and entity["dev_class"] in ("thermo_sensor", "thermostatic_radiator_valve", "zone_thermometer", "zone_thermostat")
70
82
  )
71
83
  if is_battery_low:
72
- device["binary_sensors"]["low_battery"] = True
84
+ entity["binary_sensors"]["low_battery"] = True
73
85
 
74
- self._update_for_cooling(device)
86
+ self._update_for_cooling(entity)
75
87
 
76
- remove_empty_platform_dicts(device)
88
+ remove_empty_platform_dicts(entity)
77
89
 
78
90
  def _detect_low_batteries(self) -> list[str]:
79
91
  """Helper-function updating the low-battery binary_sensor status from a Battery-is-low message."""
@@ -97,31 +109,31 @@ class SmileData(SmileHelper):
97
109
  return mac_address_list
98
110
 
99
111
  def _add_or_update_notifications(
100
- self, device_id: str, device: DeviceData, data: DeviceData
112
+ self, entity_id: str, entity: GwEntityData, data: GwEntityData
101
113
  ) -> None:
102
114
  """Helper-function adding or updating the Plugwise notifications."""
103
115
  if (
104
- device_id == self.gateway_id
116
+ entity_id == self.gateway_id
105
117
  and (
106
118
  self._is_thermostat or self.smile_type == "power"
107
119
  )
108
120
  ) or (
109
- "binary_sensors" in device
110
- and "plugwise_notification" in device["binary_sensors"]
121
+ "binary_sensors" in entity
122
+ and "plugwise_notification" in entity["binary_sensors"]
111
123
  ):
112
124
  data["binary_sensors"]["plugwise_notification"] = bool(self._notifications)
113
125
  self._count += 1
114
126
 
115
- def _update_for_cooling(self, device: DeviceData) -> None:
127
+ def _update_for_cooling(self, entity: GwEntityData) -> None:
116
128
  """Helper-function for adding/updating various cooling-related values."""
117
129
  # For Anna and heating + cooling, replace setpoint with setpoint_high/_low
118
130
  if (
119
131
  self.smile(ANNA)
120
132
  and self._cooling_present
121
- and device["dev_class"] == "thermostat"
133
+ and entity["dev_class"] == "thermostat"
122
134
  ):
123
- thermostat = device["thermostat"]
124
- sensors = device["sensors"]
135
+ thermostat = entity["thermostat"]
136
+ sensors = entity["sensors"]
125
137
  temp_dict: ActuatorData = {
126
138
  "setpoint_low": thermostat["setpoint"],
127
139
  "setpoint_high": MAX_SETPOINT,
@@ -133,53 +145,68 @@ class SmileData(SmileHelper):
133
145
  }
134
146
  thermostat.pop("setpoint")
135
147
  temp_dict.update(thermostat)
136
- device["thermostat"] = temp_dict
148
+ entity["thermostat"] = temp_dict
137
149
  if "setpoint" in sensors:
138
150
  sensors.pop("setpoint")
139
151
  sensors["setpoint_low"] = temp_dict["setpoint_low"]
140
152
  sensors["setpoint_high"] = temp_dict["setpoint_high"]
141
- self._count += 2
153
+ self._count += 2 # add 4, remove 2
154
+
155
+
156
+ def _get_location_data(self, loc_id: str) -> GwEntityData:
157
+ """Helper-function for _all_entity_data() and async_update().
158
+
159
+ Provide entity-data, based on Location ID (= loc_id).
160
+ """
161
+ zone = self._zones[loc_id]
162
+ data = self._get_zone_data(loc_id)
163
+ if ctrl_state := self._control_state(loc_id):
164
+ data["control_state"] = ctrl_state
165
+ self._count += 1
142
166
 
143
- def _get_device_data(self, dev_id: str) -> DeviceData:
144
- """Helper-function for _all_device_data() and async_update().
167
+ # Thermostat data (presets, temperatures etc)
168
+ self._climate_data(loc_id, zone, data)
169
+
170
+ return data
145
171
 
146
- Provide device-data, based on Location ID (= dev_id), from APPLIANCES.
172
+ def _get_entity_data(self, entity_id: str) -> GwEntityData:
173
+ """Helper-function for _update_gw_entities() and async_update().
174
+
175
+ Provide entity-data, based on appliance_id (= entity_id).
147
176
  """
148
- device = self.gw_devices[dev_id]
149
- data = self._get_measurement_data(dev_id)
177
+ entity = self.gw_entities[entity_id]
178
+ data = self._get_measurement_data(entity_id)
150
179
 
151
- # Check availability of wired-connected devices
180
+ # Check availability of wired-connected entities
152
181
  # Smartmeter
153
182
  self._check_availability(
154
- device, "smartmeter", data, "P1 does not seem to be connected"
183
+ entity, "smartmeter", data, "P1 does not seem to be connected"
155
184
  )
156
- # OpenTherm device
157
- if device["name"] != "OnOff":
185
+ # OpenTherm entity
186
+ if entity["name"] != "OnOff":
158
187
  self._check_availability(
159
- device, "heater_central", data, "no OpenTherm communication"
188
+ entity, "heater_central", data, "no OpenTherm communication"
160
189
  )
161
190
 
162
191
  # Switching groups data
163
- self._device_data_switching_group(device, data)
192
+ self._entity_switching_group(entity, data)
164
193
  # Adam data
165
- self._device_data_adam(device, data)
166
- # Skip obtaining data for (Adam) secondary thermostats
167
- if device["dev_class"] not in ZONE_THERMOSTATS:
168
- return data
194
+ self._get_adam_data(entity, data)
169
195
 
170
- # Thermostat data (presets, temperatures etc)
171
- self._device_data_climate(device, data)
196
+ # Thermostat data for Anna (presets, temperatures etc)
197
+ if self.smile(ANNA) and entity["dev_class"] == "thermostat":
198
+ self._climate_data(entity_id, entity, data)
172
199
 
173
200
  return data
174
201
 
175
202
  def _check_availability(
176
- self, device: DeviceData, dev_class: str, data: DeviceData, message: str
203
+ self, entity: GwEntityData, dev_class: str, data: GwEntityData, message: str
177
204
  ) -> None:
178
- """Helper-function for _get_device_data().
205
+ """Helper-function for _get_entity_data().
179
206
 
180
- Provide availability status for the wired-commected devices.
207
+ Provide availability status for the wired-connected devices.
181
208
  """
182
- if device["dev_class"] == dev_class:
209
+ if entity["dev_class"] == dev_class:
183
210
  data["available"] = True
184
211
  self._count += 1
185
212
  for item in self._notifications.values():
@@ -187,8 +214,8 @@ class SmileData(SmileHelper):
187
214
  if message in msg:
188
215
  data["available"] = False
189
216
 
190
- def _device_data_adam(self, device: DeviceData, data: DeviceData) -> None:
191
- """Helper-function for _get_device_data().
217
+ def _get_adam_data(self, entity: GwEntityData, data: GwEntityData) -> None:
218
+ """Helper-function for _get_entity_data().
192
219
 
193
220
  Determine Adam heating-status for on-off heating via valves,
194
221
  available regulations_modes and thermostat control_states.
@@ -196,14 +223,14 @@ class SmileData(SmileHelper):
196
223
  if self.smile(ADAM):
197
224
  # Indicate heating_state based on valves being open in case of city-provided heating
198
225
  if (
199
- device["dev_class"] == "heater_central"
226
+ entity["dev_class"] == "heater_central"
200
227
  and self._on_off_device
201
228
  and isinstance(self._heating_valves(), int)
202
229
  ):
203
230
  data["binary_sensors"]["heating_state"] = self._heating_valves() != 0
204
231
 
205
232
  # Show the allowed regulation_modes and gateway_modes
206
- if device["dev_class"] == "gateway":
233
+ if entity["dev_class"] == "gateway":
207
234
  if self._reg_allowed_modes:
208
235
  data["regulation_modes"] = self._reg_allowed_modes
209
236
  self._count += 1
@@ -211,19 +238,20 @@ class SmileData(SmileHelper):
211
238
  data["gateway_modes"] = self._gw_allowed_modes
212
239
  self._count += 1
213
240
 
214
- # Control_state, only available for Adam primary thermostats
215
- if device["dev_class"] in ZONE_THERMOSTATS:
216
- loc_id = device["location"]
217
- if ctrl_state := self._control_state(loc_id):
218
- data["control_state"] = ctrl_state
219
- self._count += 1
220
241
 
221
- def _device_data_climate(self, device: DeviceData, data: DeviceData) -> None:
222
- """Helper-function for _get_device_data().
242
+ def _climate_data(
243
+ self,
244
+ location_id: str,
245
+ entity: GwEntityData,
246
+ data: GwEntityData
247
+ ) -> None:
248
+ """Helper-function for _get_entity_data().
223
249
 
224
- Determine climate-control device data.
250
+ Determine climate-control entity data.
225
251
  """
226
- loc_id = device["location"]
252
+ loc_id = location_id
253
+ if entity.get("location") is not None:
254
+ loc_id = entity["location"]
227
255
 
228
256
  # Presets
229
257
  data["preset_modes"] = None
@@ -258,13 +286,13 @@ class SmileData(SmileHelper):
258
286
 
259
287
  def check_reg_mode(self, mode: str) -> bool:
260
288
  """Helper-function for device_data_climate()."""
261
- gateway = self.gw_devices[self.gateway_id]
289
+ gateway = self.gw_entities[self.gateway_id]
262
290
  return (
263
291
  "regulation_modes" in gateway and gateway["select_regulation_mode"] == mode
264
292
  )
265
293
 
266
294
  def _get_schedule_states_with_off(
267
- self, location: str, schedules: list[str], selected: str, data: DeviceData
295
+ self, location: str, schedules: list[str], selected: str, data: GwEntityData
268
296
  ) -> None:
269
297
  """Collect schedules with states for each thermostat.
270
298