Skip to content

Commit 67719f1

Browse files
ReneNulschDEclaude
andcommitted
fix: correct window cover position mapping for windowStatusOverall
The summary "windows" cover reported the inverted state: closed windows showed as 100% open and vice versa (#426). windowStatusOverall uses the WindowStatusOverall enum (0 OPEN, 1 CLOSED, 2 COMPLETELY_OPEN, 3 AIRING), which is numbered differently from the Windowstatus enum of the individual windows (0 INTERMEDIATE, 1 COMPLETELY_OPENED, 2 COMPLETELY_CLOSED, 3 AIRING_POSITION). The old mapping treated any overall value > 0 as open, so CLOSED (1) became 100% and OPEN (0) became closed. Replace the status sets with two explicit enum based position maps and ignore attributes whose retrieval status shows that the car did not report a usable value. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 8b5e63b commit 67719f1

1 file changed

Lines changed: 41 additions & 27 deletions

File tree

custom_components/mbapi2020/cover.py

Lines changed: 41 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,17 @@
2525
from .coordinator import MBAPI2020DataUpdateCoordinator
2626
from .helper import LogHelper as loghelper
2727

28-
WINDOW_STATUS_CLOSED = {"2", 2}
29-
WINDOW_STATUS_OPEN = {"1", 1}
30-
WINDOW_STATUS_VENTILATING = {"3", 3}
31-
WINDOW_STATUS_INTERMEDIATE = {"0", 0, "4", 4}
32-
WINDOW_STATUS_OVERALL_CLOSED = {"0", 0, "CLOSED", "closed"}
33-
WINDOW_STATUS_OVERALL_OPEN = {"OPEN", "open"}
28+
# Positions for the individual windows, based on the proto enum Windowstatus:
29+
# 0 INTERMEDIATE, 1 COMPLETELY_OPENED, 2 COMPLETELY_CLOSED, 3 AIRING_POSITION
30+
WINDOW_STATUS_POSITIONS: dict[int, int] = {0: 50, 1: 100, 2: 0, 3: 10}
31+
32+
# Positions for the window summary, based on the proto enum WindowStatusOverall:
33+
# 0 OPEN, 1 CLOSED, 2 COMPLETELY_OPEN, 3 AIRING
34+
# Note the numbering differs from the individual windows above
35+
WINDOW_STATUS_OVERALL_POSITIONS: dict[int, int] = {0: 50, 1: 0, 2: 100, 3: 10}
36+
37+
# Retrieval status values that indicate the car did not report a usable value
38+
WINDOW_STATUS_INVALID_RETRIEVAL_STATUS = {3, "3", 4, "4", "NOT_RECEIVED", "error"}
3439

3540

3641
@dataclass(frozen=True, kw_only=True)
@@ -117,27 +122,27 @@ def _all_windows_position_args(position: int) -> dict[str, int | None]:
117122

118123

119124
def _status_to_position(status: Any, *, overall: bool = False) -> int | None:
120-
if overall:
121-
if isinstance(status, bool):
122-
return 0 if status else 100
123-
if status in WINDOW_STATUS_OVERALL_CLOSED:
125+
"""Translate a reported window status into a cover position."""
126+
if status is None:
127+
return None
128+
129+
# The REST fallback builds a synthetic windowStatusOverall where True means
130+
# that all windows are closed
131+
if isinstance(status, bool):
132+
return 0 if status else 100
133+
134+
if isinstance(status, str):
135+
normalized = status.strip().upper()
136+
if normalized == "CLOSED":
124137
return 0
125-
if status in WINDOW_STATUS_OVERALL_OPEN:
138+
if normalized == "OPEN":
126139
return 100
127-
try:
128-
return 100 if int(status) > 0 else 0
129-
except (TypeError, ValueError):
130-
return None
131140

132-
if status in WINDOW_STATUS_CLOSED:
133-
return 0
134-
if status in WINDOW_STATUS_OPEN:
135-
return 100
136-
if status in WINDOW_STATUS_VENTILATING:
137-
return 10
138-
if status in WINDOW_STATUS_INTERMEDIATE:
139-
return 50
140-
return None
141+
positions = WINDOW_STATUS_OVERALL_POSITIONS if overall else WINDOW_STATUS_POSITIONS
142+
try:
143+
return positions.get(int(status))
144+
except (TypeError, ValueError):
145+
return None
141146

142147

143148
class MercedesMeCover(MercedesMeEntity, CoverEntity):
@@ -157,11 +162,15 @@ def __init__(self, description: MercedesMeCoverEntityDescription, vin: str, coor
157162
def supported_features(self) -> CoverEntityFeature:
158163
"""Return the supported features."""
159164
features = CoverEntityFeature(0)
160-
supports_variable_window = self._skip_capability_check or self._car.features.get("variableOpenableWindow") is True
165+
supports_variable_window = (
166+
self._skip_capability_check or self._car.features.get("variableOpenableWindow") is True
167+
)
161168

162169
if self.entity_description.key == "windows":
163170
if (
164-
self._skip_capability_check or self._car.features.get("WINDOWS_OPEN") is True or supports_variable_window
171+
self._skip_capability_check
172+
or self._car.features.get("WINDOWS_OPEN") is True
173+
or supports_variable_window
165174
):
166175
features |= CoverEntityFeature.OPEN
167176
if (
@@ -217,7 +226,12 @@ def is_closing(self) -> bool | None:
217226

218227
@property
219228
def _window_status(self) -> Any:
220-
return self._get_car_value("windows", self.entity_description.status_attribute, "value", None)
229+
status_attribute = self.entity_description.status_attribute
230+
retrieval_status = self._get_car_value("windows", status_attribute, "retrievalstatus", "error")
231+
if retrieval_status in WINDOW_STATUS_INVALID_RETRIEVAL_STATUS:
232+
return None
233+
234+
return self._get_car_value("windows", status_attribute, "value", None)
221235

222236
@property
223237
def _is_overall_cover(self) -> bool:

0 commit comments

Comments
 (0)