Skip to content

Commit 08e965b

Browse files
authored
Merge pull request #162 from plugwise/anna_fix
Anna fix
2 parents db4a8a4 + 91db9c1 commit 08e965b

18 files changed

+9343
-31
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changelog
22

3+
# v0.16.8 - Smile: bugfixes, continued
4+
- Fix for https://github.com/home-assistant/core/issues/68003
5+
- Refix solution for #158
6+
37
# v0.16.7 - Smile: Bugfixes, more changes and improvements
48
- Fix for #158: error setting up for systems with an Anna and and Elga (heatpump).
59
- Block connecting to the Anna when an Adam is present (fixes pw-beta #231).

plugwise/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""Plugwise module."""
22

3-
__version__ = "0.16.8a1"
3+
__version__ = "0.16.8"
44

55
from plugwise.smile import Smile
66
from plugwise.stick import Stick

plugwise/constants.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -437,7 +437,7 @@
437437
# Added measurements from actuator_functionalities/thermostat_functionality
438438
"lower_bound": {ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS},
439439
"upper_bound": {ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS},
440-
"resolution": {ATTR_UNIT_OF_MEASUREMENT: None},
440+
"resolution": {ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS},
441441
}
442442

443443
# Heater Central related measurements

plugwise/helper.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,9 @@ def __init__(self):
290290
"""Set the constructor for this class."""
291291
self._appl_data: dict[str, Any] = {}
292292
self._appliances: etree | None = None
293+
self._anna_cooling_present: bool = False
294+
self._cooling_activation_outdoor_temp: float | None = None
295+
self._cooling_deactivation_threshold: float | None = None
293296
self._cooling_present = False
294297
self._devices: dict[str, str] = {}
295298
self._domain_objects: etree | None = None
@@ -816,7 +819,13 @@ def _appliance_measurements(
816819
appl_p_loc.text, attrs.get(ATTR_UNIT_OF_MEASUREMENT)
817820
)
818821

819-
# Anna: use the local outdoor temperature as reference for turning cooling on/off
822+
# Anna: save cooling-related measurements for later use
823+
# Use the local outdoor temperature as reference for turning cooling on/off
824+
if measurement == "cooling_activation_outdoor_temperature":
825+
self._anna_cooling_present = self._cooling_present = True
826+
self._cooling_activation_outdoor_temp = data.get(measurement)
827+
if measurement == "cooling_deactivation_threshold":
828+
self._cooling_deactivation_threshold = data.get(measurement)
820829
if measurement == "outdoor_temperature":
821830
self._outdoor_temp = data.get(measurement)
822831

@@ -838,7 +847,7 @@ def _appliance_measurements(
838847
continue
839848

840849
data[measurement] = format_measure(
841-
t_function.text, attrs[ATTR_UNIT_OF_MEASUREMENT]
850+
t_function.text, attrs.get(ATTR_UNIT_OF_MEASUREMENT)
842851
)
843852

844853
return data

plugwise/smile.py

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,20 @@ def get_all_devices(self) -> None:
9191
# Collect data for each device via helper function
9292
self._all_device_data()
9393

94+
# Anna: indicate possible active heating/cooling operation-mode
95+
# Actual ongoing heating/cooling is shown via heating_state/cooling_state
96+
if self._anna_cooling_present:
97+
if (
98+
not self.cooling_active
99+
and self._outdoor_temp > self._cooling_activation_outdoor_temp
100+
):
101+
self.cooling_active = True
102+
if (
103+
self.cooling_active
104+
and self._outdoor_temp < self._cooling_deactivation_threshold
105+
):
106+
self.cooling_active = False
107+
94108
# Don't show cooling_state when no cooling present
95109
for _, device in self.gw_devices.items():
96110
if (
@@ -161,19 +175,6 @@ def _device_data_climate(
161175
if ctrl_state := self._control_state(loc_id):
162176
device_data["control_state"] = ctrl_state
163177

164-
# Anna: indicate possible active heating/cooling operation-mode
165-
# Actual ongoing heating/cooling is shown via heating_state/cooling_state
166-
if "cooling_activation_outdoor_temperature" in device_data:
167-
self._cooling_present = True
168-
if not self.cooling_active and self._outdoor_temp > device_data.get(
169-
"cooling_activation_outdoor_temperature"
170-
):
171-
self.cooling_active = True
172-
if self.cooling_active and self._outdoor_temp < device_data.get(
173-
"cooling_deactivation_threshold"
174-
):
175-
self.cooling_active = False
176-
177178
# Operation mode: auto, heat, cool
178179
device_data["mode"] = "auto"
179180
if sel_schema == "None":

plugwise/util.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
LOGADDR_OFFSET,
1919
PERCENTAGE,
2020
PLUGWISE_EPOCH,
21+
TEMP_CELSIUS,
2122
UTF8_DECODE,
2223
VOLUME_CUBIC_METERS,
2324
)
@@ -98,6 +99,8 @@ def format_measure(measure: str, unit: str) -> float | int | bool:
9899
"""Format measure to correct type."""
99100
try:
100101
measure = int(measure)
102+
if unit == TEMP_CELSIUS:
103+
measure = float(measure)
101104
except ValueError:
102105
if unit == PERCENTAGE:
103106
if 0 < float(measure) <= 1:

tests/test_smile.py

Lines changed: 158 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -250,9 +250,7 @@ async def connect(
250250
client = aiohttp.test_utils.TestClient(server)
251251
websession = client.session
252252

253-
url = "{}://{}:{}/core/locations".format(
254-
server.scheme, server.host, server.port
255-
)
253+
url = f"{server.scheme}://{server.host}:{server.port}/core/locations"
256254

257255
# Try/exceptpass to accommodate for Timeout of aoihttp
258256
try:
@@ -376,15 +374,11 @@ def show_setup(location_list, device_list):
376374
assert False
377375

378376
@pytest.mark.asyncio
379-
async def device_test(self, smile=pw_smile.Smile, testdata=None, preset=False):
377+
async def device_test(self, smile=pw_smile.Smile, testdata=None):
380378
"""Perform basic device tests."""
381379
_LOGGER.info("Asserting testdata:")
382380
bsw_list = ["binary_sensors", "central", "climate", "sensors", "switches"]
383381
smile.get_all_devices()
384-
# Preset smile.cooling_active for testing of a state-change
385-
smile.cooling_active = False
386-
if preset:
387-
smile.cooling_active = True
388382
data = await smile.async_update()
389383
extra = data[0]
390384
device_list = data[1]
@@ -426,9 +420,7 @@ async def device_test(self, smile=pw_smile.Smile, testdata=None, preset=False):
426420
for measure_key, measure_assert in measurements.items():
427421
_LOGGER.info(
428422
"%s",
429-
" + Testing {} (should be {})".format(
430-
measure_key, measure_assert
431-
),
423+
f" + Testing {measure_key} (should be {measure_assert})",
432424
)
433425
tests += 1
434426
if measure_key in bsw_list:
@@ -2151,7 +2143,7 @@ async def test_connect_anna_heatpump(self):
21512143
assert not smile._smile_legacy
21522144

21532145
# Preset cooling_active to True, will turn to False due to the lowered outdoor temp
2154-
await self.device_test(smile, testdata, True)
2146+
await self.device_test(smile, testdata)
21552147
assert self.cooling_present
21562148
assert not self.notifications
21572149

@@ -2160,7 +2152,12 @@ async def test_connect_anna_heatpump(self):
21602152

21612153
@pytest.mark.asyncio
21622154
async def test_connect_anna_heatpump_cooling(self):
2163-
"""Test a Anna with Elga setup in cooling mode."""
2155+
"""
2156+
Test an Anna with Elga setup in cooling mode.
2157+
This test also covers the situation that the operation-mode it switched
2158+
from heating to cooliing due to the outdoor temperature rising above the
2159+
cooling_activation_outdoor_temperature threshold.
2160+
"""
21642161
testdata = {
21652162
# Anna
21662163
"3cb70739631c4d17a86b8b12e8a5161b": {
@@ -2211,6 +2208,153 @@ async def test_connect_anna_heatpump_cooling(self):
22112208
await smile.close_connection()
22122209
await self.disconnect(server, client)
22132210

2211+
@pytest.mark.asyncio
2212+
async def test_connect_anna_heatpump_cooling_to_off(self):
2213+
"""
2214+
This test covers the situation that the operation-mode it switched back
2215+
from cooling to heating due to the outdoor temperature dropping below the
2216+
cooling_deactivation_threshold.
2217+
"""
2218+
testdata = {
2219+
# Anna
2220+
"3cb70739631c4d17a86b8b12e8a5161b": {
2221+
"selected_schedule": "None",
2222+
"active_preset": "home",
2223+
"mode": "heat",
2224+
"sensors": {
2225+
"illuminance": 25.5,
2226+
"cooling_activation_outdoor_temperature": 21.0,
2227+
"cooling_deactivation_threshold": 6,
2228+
},
2229+
},
2230+
# Heater central
2231+
"1cbf783bb11e4a7c8a6843dee3a86927": {
2232+
"binary_sensors": {
2233+
"cooling_state": False,
2234+
"dhw_state": False,
2235+
"heating_state": False,
2236+
},
2237+
"sensors": {
2238+
"outdoor_temperature": 3.0,
2239+
"water_temperature": 24.7,
2240+
"water_pressure": 1.61,
2241+
},
2242+
},
2243+
# Gateway
2244+
"015ae9ea3f964e668e490fa39da3870b": {
2245+
"sensors": {"outdoor_temperature": 22.0}
2246+
},
2247+
}
2248+
2249+
self.smile_setup = "anna_heatpump_cooling_to_off"
2250+
server, smile, client = await self.connect_wrapper()
2251+
2252+
smile.cooling_active = True
2253+
await self.device_test(smile, testdata)
2254+
await smile.close_connection()
2255+
await self.disconnect(server, client)
2256+
2257+
@pytest.mark.asyncio
2258+
async def test_connect_anna_elga_2(self):
2259+
"""Test a Anna with Elga setup in cooling mode (with missing outdoor temperature - solved)."""
2260+
testdata = {
2261+
# Anna
2262+
"ebd90df1ab334565b5895f37590ccff4": {
2263+
"class": "thermostat",
2264+
"fw": "2018-02-08T11:15:53+01:00",
2265+
"hw": "6539-1301-5002",
2266+
"location": "d3ce834534114348be628b61b26d9220",
2267+
"mac_address": None,
2268+
"model": "Anna",
2269+
"name": "Anna",
2270+
"vendor": "Plugwise",
2271+
"lower_bound": 4,
2272+
"upper_bound": 30,
2273+
"resolution": 0.1,
2274+
"preset_modes": ["away", "no_frost", "vacation", "home", "asleep"],
2275+
"active_preset": "home",
2276+
"presets": {
2277+
"away": [15.0, 25.0],
2278+
"no_frost": [10.0, 30.0],
2279+
"vacation": [15.0, 27.0],
2280+
"home": [19.5, 23.0],
2281+
"asleep": [19.0, 23.0],
2282+
},
2283+
"available_schedules": ["Thermostat schedule"],
2284+
"selected_schedule": "Thermostat schedule",
2285+
"last_used": "Thermostat schedule",
2286+
"schedule_temperature": 19.5,
2287+
"mode": "auto",
2288+
"sensors": {
2289+
"temperature": 20.9,
2290+
"setpoint": 19.5,
2291+
"illuminance": 0.5,
2292+
"cooling_activation_outdoor_temperature": 26.0,
2293+
"cooling_deactivation_threshold": 3,
2294+
},
2295+
},
2296+
# Heater central
2297+
"573c152e7d4f4720878222bd75638f5b": {
2298+
"class": "heater_central",
2299+
"fw": None,
2300+
"hw": None,
2301+
"location": "d34dfe6ab90b410c98068e75de3eb631",
2302+
"mac_address": None,
2303+
"model": "Generic heater",
2304+
"name": "OpenTherm",
2305+
"vendor": "Techneco",
2306+
"compressor_state": False,
2307+
"binary_sensors": {
2308+
"dhw_state": False,
2309+
"heating_state": False,
2310+
"cooling_state": False,
2311+
"slave_boiler_state": False,
2312+
"flame_state": False,
2313+
},
2314+
"sensors": {
2315+
"outdoor_temperature": 14.0,
2316+
"water_temperature": 22.8,
2317+
"intended_boiler_temperature": 0.0,
2318+
"modulation_level": 0.0,
2319+
"return_temperature": 23.4,
2320+
"water_pressure": 0.5,
2321+
},
2322+
"switches": {"dhw_cm_switch": True},
2323+
},
2324+
# Gateway
2325+
"fb49af122f6e4b0f91267e1cf7666d6f": {
2326+
"class": "gateway",
2327+
"fw": "4.2.1",
2328+
"hw": "AME Smile 2.0 board",
2329+
"location": "d34dfe6ab90b410c98068e75de3eb631",
2330+
"mac_address": "C4930002FE76",
2331+
"model": "Anna",
2332+
"name": "Anna",
2333+
"vendor": "Plugwise B.V.",
2334+
"binary_sensors": {"plugwise_notification": False},
2335+
"sensors": {"outdoor_temperature": 13.0},
2336+
},
2337+
}
2338+
2339+
self.smile_setup = "anna_elga_2"
2340+
server, smile, client = await self.connect_wrapper()
2341+
assert smile.smile_hostname == "smile000000"
2342+
2343+
_LOGGER.info("Basics:")
2344+
_LOGGER.info(" # Assert type = thermostat")
2345+
assert smile.smile_type == "thermostat"
2346+
_LOGGER.info(" # Assert version")
2347+
assert smile.smile_version[0] == "4.2.1"
2348+
_LOGGER.info(" # Assert no legacy")
2349+
assert not smile._smile_legacy
2350+
2351+
await self.device_test(smile, testdata)
2352+
assert self.cooling_present
2353+
assert not self.notifications
2354+
2355+
await smile.close_connection()
2356+
await self.disconnect(server, client)
2357+
22142358
@pytest.mark.asyncio
22152359
async def test_connect_adam_plus_anna_copy_with_error_domain_added(self):
22162360
"""Test erroneous domain_objects file from user."""
@@ -2233,7 +2377,7 @@ async def test_connect_adam_plus_anna_copy_with_error_domain_added(self):
22332377
_LOGGER.info(" # Assert legacy")
22342378
assert not smile._smile_legacy
22352379

2236-
await self.device_test(smile, testdata, True)
2380+
await self.device_test(smile, testdata)
22372381

22382382
assert "3d28a20e17cb47dca210a132463721d5" in self.notifications
22392383

userdata/anna_elga_2/@bart1970

Whitespace-only changes.

0 commit comments

Comments
 (0)