forked from erwindouna/python-melcloud
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy patherv_device.py
More file actions
241 lines (187 loc) · 7.39 KB
/
Copy patherv_device.py
File metadata and controls
241 lines (187 loc) · 7.39 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
"""Energy-Recovery-Ventilation (DeviceType=3) device definition."""
from typing import Any, Dict, List, Optional
from pymelcloud.device import EFFECTIVE_FLAGS, Device
PROPERTY_VENTILATION_MODE = "ventilation_mode"
PROPERTY_FAN_SPEED = "fan_speed"
FAN_SPEED_UNDEFINED = "undefined"
FAN_SPEED_STOPPED = "0"
VENTILATION_MODE_RECOVERY = "recovery"
VENTILATION_MODE_BYPASS = "bypass"
VENTILATION_MODE_AUTO = "auto"
VENTILATION_MODE_UNDEFINED = "undefined"
_VENTILATION_MODE_LOOKUP = {
0: VENTILATION_MODE_RECOVERY,
1: VENTILATION_MODE_BYPASS,
2: VENTILATION_MODE_AUTO,
}
def _fan_speed_from(speed: int) -> str:
if speed == -1:
return FAN_SPEED_UNDEFINED
if speed == 0:
return FAN_SPEED_STOPPED
return str(speed)
def _fan_speed_to(speed: str) -> int:
if speed == FAN_SPEED_UNDEFINED:
return -1
if speed == FAN_SPEED_STOPPED:
return 0
return int(speed)
def _ventilation_mode_from(mode: int) -> str:
return _VENTILATION_MODE_LOOKUP.get(mode, VENTILATION_MODE_UNDEFINED)
def _ventilation_mode_to(mode: str) -> int:
for k, value in _VENTILATION_MODE_LOOKUP.items():
if value == mode:
return k
raise ValueError(f"Invalid ventilation_mode [{mode}]")
class ErvDevice(Device):
"""Energy-Recovery-Ventilation device."""
def apply_write(self, state: Dict[str, Any], key: str, value: Any):
"""Apply writes to state object.
Used for property validation, do not modify device state.
"""
flags = state.get(EFFECTIVE_FLAGS, 0)
if key == PROPERTY_VENTILATION_MODE:
state["VentilationMode"] = _ventilation_mode_to(value)
flags = flags | 0x04
elif key == PROPERTY_FAN_SPEED:
state["SetFanSpeed"] = _fan_speed_to(value)
flags = flags | 0x08
else:
raise ValueError(f"Cannot set {key}, invalid property")
state[EFFECTIVE_FLAGS] = flags
def _device(self) -> Dict[str, Any]:
return self._device_conf.get("Device", {})
@property
def has_energy_consumed_meter(self) -> bool:
"""Return True if the device has an energy consumption meter."""
if self._device_conf is None:
return False
return self._device().get("HasEnergyConsumedMeter", False)
@property
def total_energy_consumed(self) -> Optional[float]:
"""Return total consumed energy as kWh.
The update interval is extremely slow and inconsistent. Empirical evidence
suggests can vary between 1h 30min and 3h.
"""
if self._device_conf is None:
return None
reading = self._device().get("CurrentEnergyConsumed", None)
if reading is None:
return None
return reading / 1000.0
@property
def presets(self) -> List[Dict[Any, Any]]:
"""Return presets configuration (preset created using melcloud app)."""
retval = []
if self._device_conf is not None:
presets_conf = self._device_conf.get("Presets", {})
for p in presets_conf:
retval.append(p)
return retval
@property
def room_temperature(self) -> Optional[float]:
"""Return room temperature reported by the device."""
if self._state is None:
return None
return self._state.get("RoomTemperature")
@property
def outside_temperature(self) -> Optional[float]:
"""Return outdoor temperature reported by the device."""
if self._state is None:
return None
return self._state.get("OutdoorTemperature")
@property
def ventilation_mode(self) -> Optional[str]:
"""Return currently active ventilation mode."""
if self._state is None:
return None
return _ventilation_mode_from(self._state.get("VentilationMode", -1))
@property
def actual_ventilation_mode(self) -> Optional[str]:
"""Return actual ventilation mode."""
if self._state is None:
return None
return _ventilation_mode_from(self._device().get("ActualVentilationMode", -1))
@property
def fan_speed(self) -> Optional[str]:
"""Return currently active fan speed.
The argument must be one of the fan speeds returned by fan_speeds.
"""
if self._state is None:
return None
return _fan_speed_from(self._state.get("SetFanSpeed", -1))
@property
def actual_supply_fan_speed(self) -> Optional[str]:
"""Return actual supply fan speed.
The argument must be one of the fan speeds returned by fan_speeds.
"""
if self._state is None:
return None
return _fan_speed_from(self._device().get("ActualSupplyFanSpeed", -1))
@property
def actual_exhaust_fan_speed(self) -> Optional[str]:
"""Return actual exhaust fan speed.
The argument must be one of the fan speeds returned by fan_speeds.
"""
if self._state is None:
return None
return _fan_speed_from(self._device().get("ActualExhaustFanSpeed", -1))
@property
def core_maintenance_required(self) -> bool:
"""Return True if core maintenance required."""
if self._device_conf is None:
return False
return self._device().get("CoreMaintenanceRequired", False)
@property
def filter_maintenance_required(self) -> bool:
"""Return True if filter maintenance required."""
if self._device_conf is None:
return False
return self._device().get("FilterMaintenanceRequired", False)
@property
def night_purge_mode(self) -> bool:
"""Return True if NightPurgeMode."""
if self._device_conf is None:
return False
return self._device().get("NightPurgeMode", False)
@property
def room_co2_level(self) -> Optional[float]:
"""Return co2 level if supported by the device."""
if self._state is None:
return None
if not self._state.get("HasCO2Sensor", False):
return None
return self._device().get("RoomCO2Level", None)
@property
def fan_speeds(self) -> Optional[List[str]]:
"""Return available fan speeds.
The supported fan speeds vary from device to device. The available modes are
read from the Device capability attributes.
For example, a 5 speed device with auto fan speed would produce the following
list (formatted '"[pymelcloud]" -- "[device controls]"')
- "auto" -- "auto"
- "1" -- "silent"
- "2" -- "1"
- "3" -- "2"
- "4" -- "3"
- "5" -- "4"
MELCloud is not aware of the device type making it infeasible to match the
fan speed names with the device documentation.
"""
if self._state is None:
return None
speeds = []
num_fan_speeds = self._state.get("NumberOfFanSpeeds", 0)
for num in range(1, num_fan_speeds + 1):
speeds.append(_fan_speed_from(num))
return speeds
@property
def ventilation_modes(self) -> List[str]:
"""Return available ventilation modes."""
modes: List[str] = [VENTILATION_MODE_RECOVERY]
device = self._device()
if device.get("HasBypassVentilationMode", False):
modes.append(VENTILATION_MODE_BYPASS)
if device.get("HasAutoVentilationMode", False):
modes.append(VENTILATION_MODE_AUTO)
return modes