pycupra 0.0.1__py3-none-any.whl → 0.0.3__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.
- pycupra/__version__.py +1 -1
- pycupra/dashboard.py +1287 -1258
- pycupra/vehicle.py +13 -11
- {pycupra-0.0.1.dist-info → pycupra-0.0.3.dist-info}/METADATA +1 -1
- pycupra-0.0.3.dist-info/RECORD +13 -0
- pycupra-0.0.1.dist-info/RECORD +0 -13
- {pycupra-0.0.1.dist-info → pycupra-0.0.3.dist-info}/WHEEL +0 -0
- {pycupra-0.0.1.dist-info → pycupra-0.0.3.dist-info}/licenses/LICENSE +0 -0
- {pycupra-0.0.1.dist-info → pycupra-0.0.3.dist-info}/top_level.txt +0 -0
pycupra/dashboard.py
CHANGED
@@ -1,1258 +1,1287 @@
|
|
1
|
-
# Utilities for integration with Home Assistant
|
2
|
-
# Thanks to molobrakos and Farfar
|
3
|
-
|
4
|
-
import logging
|
5
|
-
from datetime import datetime
|
6
|
-
from .utilities import camel2slug
|
7
|
-
|
8
|
-
_LOGGER = logging.getLogger(__name__)
|
9
|
-
|
10
|
-
class Instrument:
|
11
|
-
def __init__(self, component, attr, name, icon=None):
|
12
|
-
self.attr = attr
|
13
|
-
self.component = component
|
14
|
-
self.name = name
|
15
|
-
self.vehicle = None
|
16
|
-
self.icon = icon
|
17
|
-
self.callback = None
|
18
|
-
|
19
|
-
def __repr__(self):
|
20
|
-
return self.full_name
|
21
|
-
|
22
|
-
def configurate(self, **args):
|
23
|
-
pass
|
24
|
-
|
25
|
-
@property
|
26
|
-
def slug_attr(self):
|
27
|
-
return camel2slug(self.attr.replace(".", "_"))
|
28
|
-
|
29
|
-
def setup(self, vehicle, **config):
|
30
|
-
self.vehicle = vehicle
|
31
|
-
if not self.is_supported:
|
32
|
-
return False
|
33
|
-
|
34
|
-
self.configurate(**config)
|
35
|
-
return True
|
36
|
-
|
37
|
-
@property
|
38
|
-
def vehicle_name(self):
|
39
|
-
return self.vehicle.vin
|
40
|
-
|
41
|
-
@property
|
42
|
-
def full_name(self):
|
43
|
-
return f"{self.vehicle_name} {self.name}"
|
44
|
-
|
45
|
-
@property
|
46
|
-
def is_mutable(self):
|
47
|
-
raise NotImplementedError("Must be set")
|
48
|
-
|
49
|
-
@property
|
50
|
-
def str_state(self):
|
51
|
-
return self.state
|
52
|
-
|
53
|
-
@property
|
54
|
-
def state(self):
|
55
|
-
if hasattr(self.vehicle, self.attr):
|
56
|
-
return getattr(self.vehicle, self.attr)
|
57
|
-
else:
|
58
|
-
_LOGGER.debug(f'Could not find attribute "{self.attr}"')
|
59
|
-
return self.vehicle.get_attr(self.attr)
|
60
|
-
|
61
|
-
@property
|
62
|
-
def attributes(self):
|
63
|
-
return {}
|
64
|
-
|
65
|
-
@property
|
66
|
-
def is_supported(self):
|
67
|
-
supported = 'is_' + self.attr + "_supported"
|
68
|
-
if hasattr(self.vehicle, supported):
|
69
|
-
return getattr(self.vehicle, supported)
|
70
|
-
else:
|
71
|
-
return False
|
72
|
-
|
73
|
-
|
74
|
-
class Sensor(Instrument):
|
75
|
-
def __init__(self, attr, name, icon, unit=None, device_class=None):
|
76
|
-
super().__init__(component="sensor", attr=attr, name=name, icon=icon)
|
77
|
-
self.device_class = device_class
|
78
|
-
self.unit = unit
|
79
|
-
self.convert = False
|
80
|
-
|
81
|
-
def configurate(self, **config):
|
82
|
-
if self.unit and config.get('miles', False) is True:
|
83
|
-
if "km" == self.unit:
|
84
|
-
self.unit = "mi"
|
85
|
-
self.convert = True
|
86
|
-
elif "km/h" == self.unit:
|
87
|
-
self.unit = "mi/h"
|
88
|
-
self.convert = True
|
89
|
-
elif "l/
|
90
|
-
self.unit = "l/100 mi"
|
91
|
-
self.convert = True
|
92
|
-
elif "kWh/
|
93
|
-
self.unit = "kWh/100 mi"
|
94
|
-
self.convert = True
|
95
|
-
elif self.unit and config.get('scandinavian_miles', False) is True:
|
96
|
-
if "km" == self.unit:
|
97
|
-
self.unit = "mil"
|
98
|
-
elif "km/h" == self.unit:
|
99
|
-
self.unit = "mil/h"
|
100
|
-
elif "l/
|
101
|
-
self.unit = "l/100 mil"
|
102
|
-
elif "kWh/
|
103
|
-
self.unit = "kWh/100 mil"
|
104
|
-
|
105
|
-
# Init placeholder for parking heater duration
|
106
|
-
config.get('parkingheater', 30)
|
107
|
-
if "pheater_duration" == self.attr:
|
108
|
-
setValue = config.get('climatisation_duration', 30)
|
109
|
-
self.vehicle.pheater_duration = setValue
|
110
|
-
|
111
|
-
@property
|
112
|
-
def is_mutable(self):
|
113
|
-
return False
|
114
|
-
|
115
|
-
@property
|
116
|
-
def str_state(self):
|
117
|
-
if self.unit:
|
118
|
-
return f'{self.state} {self.unit}'
|
119
|
-
else:
|
120
|
-
return f'{self.state}'
|
121
|
-
|
122
|
-
@property
|
123
|
-
def state(self):
|
124
|
-
val = super().state
|
125
|
-
# Convert to miles
|
126
|
-
if val and self.unit and "mi" in self.unit and self.convert is True:
|
127
|
-
return int(round(val / 1.609344))
|
128
|
-
elif val and self.unit and "mi/h" in self.unit and self.convert is True:
|
129
|
-
return int(round(val / 1.609344))
|
130
|
-
elif val and self.unit and "gal/100 mi" in self.unit and self.convert is True:
|
131
|
-
return round(val * 0.4251438, 1)
|
132
|
-
elif val and self.unit and "kWh/100 mi" in self.unit and self.convert is True:
|
133
|
-
return round(val * 0.4251438, 1)
|
134
|
-
elif val and self.unit and "°F" in self.unit and self.convert is True:
|
135
|
-
temp = round((val * 9 / 5) + 32, 1)
|
136
|
-
return temp
|
137
|
-
elif val and self.unit in ['mil', 'mil/h']:
|
138
|
-
return val / 10
|
139
|
-
else:
|
140
|
-
return val
|
141
|
-
|
142
|
-
|
143
|
-
class BinarySensor(Instrument):
|
144
|
-
def __init__(self, attr, name, device_class, icon='', reverse_state=False):
|
145
|
-
super().__init__(component="binary_sensor", attr=attr, name=name, icon=icon)
|
146
|
-
self.device_class = device_class
|
147
|
-
self.reverse_state = reverse_state
|
148
|
-
|
149
|
-
@property
|
150
|
-
def is_mutable(self):
|
151
|
-
return False
|
152
|
-
|
153
|
-
@property
|
154
|
-
def str_state(self):
|
155
|
-
if self.device_class in ["door", "window"]:
|
156
|
-
return "Closed" if self.state else "Open"
|
157
|
-
if self.device_class == "lock":
|
158
|
-
return "Locked" if self.state else "Unlocked"
|
159
|
-
if self.device_class == "safety":
|
160
|
-
return "Warning!" if self.state else "OK"
|
161
|
-
if self.device_class == "plug":
|
162
|
-
return "Connected" if self.state else "Disconnected"
|
163
|
-
if self.state is None:
|
164
|
-
_LOGGER.error(f"Can not encode state {self.attr} {self.state}")
|
165
|
-
return "?"
|
166
|
-
return "On" if self.state else "Off"
|
167
|
-
|
168
|
-
@property
|
169
|
-
def state(self):
|
170
|
-
val = super().state
|
171
|
-
|
172
|
-
if isinstance(val, (bool, list)):
|
173
|
-
if self.reverse_state:
|
174
|
-
if bool(val):
|
175
|
-
return False
|
176
|
-
else:
|
177
|
-
return True
|
178
|
-
else:
|
179
|
-
return bool(val)
|
180
|
-
elif isinstance(val, str):
|
181
|
-
return val != "Normal"
|
182
|
-
return val
|
183
|
-
|
184
|
-
@property
|
185
|
-
def is_on(self):
|
186
|
-
return self.state
|
187
|
-
|
188
|
-
|
189
|
-
class Switch(Instrument):
|
190
|
-
def __init__(self, attr, name, icon):
|
191
|
-
super().__init__(component="switch", attr=attr, name=name, icon=icon)
|
192
|
-
|
193
|
-
@property
|
194
|
-
def is_mutable(self):
|
195
|
-
return True
|
196
|
-
|
197
|
-
@property
|
198
|
-
def str_state(self):
|
199
|
-
return "On" if self.state else "Off"
|
200
|
-
|
201
|
-
def is_on(self):
|
202
|
-
return self.state
|
203
|
-
|
204
|
-
def turn_on(self):
|
205
|
-
pass
|
206
|
-
|
207
|
-
def turn_off(self):
|
208
|
-
pass
|
209
|
-
|
210
|
-
@property
|
211
|
-
def assumed_state(self):
|
212
|
-
return True
|
213
|
-
|
214
|
-
|
215
|
-
class Climate(Instrument):
|
216
|
-
def __init__(self, attr, name, icon):
|
217
|
-
super().__init__(component="climate", attr=attr, name=name, icon=icon)
|
218
|
-
|
219
|
-
@property
|
220
|
-
def hvac_mode(self):
|
221
|
-
pass
|
222
|
-
|
223
|
-
@property
|
224
|
-
def target_temperature(self):
|
225
|
-
pass
|
226
|
-
|
227
|
-
def set_temperature(self, **kwargs):
|
228
|
-
pass
|
229
|
-
|
230
|
-
def set_hvac_mode(self, hvac_mode):
|
231
|
-
pass
|
232
|
-
|
233
|
-
|
234
|
-
class ElectricClimatisationClimate(Climate):
|
235
|
-
def __init__(self):
|
236
|
-
super().__init__(attr="electric_climatisation", name="Electric Climatisation", icon="mdi:radiator")
|
237
|
-
|
238
|
-
@property
|
239
|
-
def hvac_mode(self):
|
240
|
-
return self.vehicle.electric_climatisation
|
241
|
-
|
242
|
-
@property
|
243
|
-
def target_temperature(self):
|
244
|
-
return self.vehicle.climatisation_target_temperature
|
245
|
-
|
246
|
-
async def set_temperature(self, temperature):
|
247
|
-
await self.vehicle.climatisation_target(temperature)
|
248
|
-
|
249
|
-
async def set_hvac_mode(self, hvac_mode):
|
250
|
-
if hvac_mode:
|
251
|
-
await self.vehicle.climatisation('electric')
|
252
|
-
else:
|
253
|
-
await self.vehicle.climatisation('off')
|
254
|
-
|
255
|
-
|
256
|
-
class CombustionClimatisationClimate(Climate):
|
257
|
-
def __init__(self):
|
258
|
-
super().__init__(attr="pheater_heating", name="Parking Heater Climatisation", icon="mdi:radiator")
|
259
|
-
|
260
|
-
def configurate(self, **config):
|
261
|
-
self.spin = config.get('spin', '')
|
262
|
-
self.duration = config.get('combustionengineheatingduration', 30)
|
263
|
-
|
264
|
-
@property
|
265
|
-
def hvac_mode(self):
|
266
|
-
return self.vehicle.pheater_heating
|
267
|
-
|
268
|
-
@property
|
269
|
-
def target_temperature(self):
|
270
|
-
return self.vehicle.climatisation_target_temperature
|
271
|
-
|
272
|
-
async def set_temperature(self, temperature):
|
273
|
-
await self.vehicle.setClimatisationTargetTemperature(temperature)
|
274
|
-
|
275
|
-
async def set_hvac_mode(self, hvac_mode):
|
276
|
-
if hvac_mode:
|
277
|
-
await self.vehicle.pheater_climatisation(spin=self.spin, duration=self.duration, mode='heating')
|
278
|
-
else:
|
279
|
-
await self.vehicle.pheater_climatisation(spin=self.spin, mode='off')
|
280
|
-
|
281
|
-
|
282
|
-
class Position(Instrument):
|
283
|
-
def __init__(self):
|
284
|
-
super().__init__(component="device_tracker", attr="position", name="Position")
|
285
|
-
|
286
|
-
@property
|
287
|
-
def is_mutable(self):
|
288
|
-
return False
|
289
|
-
|
290
|
-
@property
|
291
|
-
def state(self):
|
292
|
-
state = super().state #or {}
|
293
|
-
return (
|
294
|
-
state.get("lat", "?"),
|
295
|
-
state.get("lng", "?"),
|
296
|
-
state.get("address", "?"),
|
297
|
-
state.get("timestamp", None),
|
298
|
-
)
|
299
|
-
|
300
|
-
@property
|
301
|
-
def str_state(self):
|
302
|
-
state = super().state #or {}
|
303
|
-
ts = state.get("timestamp", None)
|
304
|
-
if isinstance(ts, str):
|
305
|
-
time = str(datetime.strptime(ts,'%Y-%m-%dT%H:%M:%SZ').astimezone(tz=None))
|
306
|
-
elif isinstance(ts, datetime):
|
307
|
-
time = str(ts.astimezone(tz=None))
|
308
|
-
else:
|
309
|
-
time = None
|
310
|
-
return (
|
311
|
-
state.get("lat", "?"),
|
312
|
-
state.get("lng", "?"),
|
313
|
-
state.get("address", "?"),
|
314
|
-
time,
|
315
|
-
)
|
316
|
-
|
317
|
-
|
318
|
-
class DoorLock(Instrument):
|
319
|
-
def __init__(self):
|
320
|
-
super().__init__(component="lock", attr="door_locked", name="Door locked")
|
321
|
-
|
322
|
-
def configurate(self, **config):
|
323
|
-
self.spin = config.get('spin', '')
|
324
|
-
|
325
|
-
@property
|
326
|
-
def is_mutable(self):
|
327
|
-
return True
|
328
|
-
|
329
|
-
@property
|
330
|
-
def str_state(self):
|
331
|
-
return "Locked" if self.state else "Unlocked"
|
332
|
-
|
333
|
-
@property
|
334
|
-
def state(self):
|
335
|
-
return self.vehicle.door_locked
|
336
|
-
|
337
|
-
@property
|
338
|
-
def is_locked(self):
|
339
|
-
return self.state
|
340
|
-
|
341
|
-
async def lock(self):
|
342
|
-
try:
|
343
|
-
response = await self.vehicle.set_lock('lock', self.spin)
|
344
|
-
await self.vehicle.update()
|
345
|
-
if self.callback is not None:
|
346
|
-
self.callback()
|
347
|
-
return response
|
348
|
-
except Exception as e:
|
349
|
-
_LOGGER.error(f"Lock failed: {e}")
|
350
|
-
return False
|
351
|
-
|
352
|
-
async def unlock(self):
|
353
|
-
try:
|
354
|
-
response = await self.vehicle.set_lock('unlock', self.spin)
|
355
|
-
await self.vehicle.update()
|
356
|
-
if self.callback is not None:
|
357
|
-
self.callback()
|
358
|
-
return response
|
359
|
-
except Exception as e:
|
360
|
-
_LOGGER.error(f"Unlock failed: {e}")
|
361
|
-
return False
|
362
|
-
|
363
|
-
@property
|
364
|
-
def attributes(self):
|
365
|
-
return dict(last_result = self.vehicle.lock_action_status)
|
366
|
-
|
367
|
-
|
368
|
-
class TrunkLock(Instrument):
|
369
|
-
def __init__(self):
|
370
|
-
super().__init__(component="lock", attr="trunk_locked", name="Trunk locked")
|
371
|
-
|
372
|
-
@property
|
373
|
-
def is_mutable(self):
|
374
|
-
return True
|
375
|
-
|
376
|
-
@property
|
377
|
-
def str_state(self):
|
378
|
-
return "Locked" if self.state else "Unlocked"
|
379
|
-
|
380
|
-
@property
|
381
|
-
def state(self):
|
382
|
-
return self.vehicle.trunk_locked
|
383
|
-
|
384
|
-
@property
|
385
|
-
def is_locked(self):
|
386
|
-
return self.state
|
387
|
-
|
388
|
-
async def lock(self):
|
389
|
-
return None
|
390
|
-
|
391
|
-
async def unlock(self):
|
392
|
-
return None
|
393
|
-
|
394
|
-
# Switches
|
395
|
-
class RequestHonkAndFlash(Switch):
|
396
|
-
def __init__(self):
|
397
|
-
super().__init__(attr="request_honkandflash", name="Start honking and flashing", icon="mdi:car-emergency")
|
398
|
-
|
399
|
-
@property
|
400
|
-
def state(self):
|
401
|
-
return self.vehicle.request_honkandflash
|
402
|
-
|
403
|
-
async def turn_on(self):
|
404
|
-
await self.vehicle.set_honkandflash('honkandflash')
|
405
|
-
await self.vehicle.update()
|
406
|
-
if self.callback is not None:
|
407
|
-
self.callback()
|
408
|
-
|
409
|
-
async def turn_off(self):
|
410
|
-
pass
|
411
|
-
|
412
|
-
@property
|
413
|
-
def assumed_state(self):
|
414
|
-
return False
|
415
|
-
|
416
|
-
@property
|
417
|
-
def attributes(self):
|
418
|
-
return dict(last_result = self.vehicle.honkandflash_action_status)
|
419
|
-
|
420
|
-
|
421
|
-
class RequestFlash(Switch):
|
422
|
-
def __init__(self):
|
423
|
-
super().__init__(attr="request_flash", name="Start flashing", icon="mdi:car-parking-lights")
|
424
|
-
|
425
|
-
@property
|
426
|
-
def state(self):
|
427
|
-
return self.vehicle.request_flash
|
428
|
-
|
429
|
-
async def turn_on(self):
|
430
|
-
await self.vehicle.set_honkandflash('flash')
|
431
|
-
await self.vehicle.update()
|
432
|
-
if self.callback is not None:
|
433
|
-
self.callback()
|
434
|
-
|
435
|
-
async def turn_off(self):
|
436
|
-
pass
|
437
|
-
|
438
|
-
@property
|
439
|
-
def assumed_state(self):
|
440
|
-
return False
|
441
|
-
|
442
|
-
@property
|
443
|
-
def attributes(self):
|
444
|
-
return dict(last_result = self.vehicle.honkandflash_action_status)
|
445
|
-
|
446
|
-
|
447
|
-
class RequestUpdate(Switch):
|
448
|
-
def __init__(self):
|
449
|
-
super().__init__(attr="refresh_data", name="Force data refresh", icon="mdi:car-connected")
|
450
|
-
|
451
|
-
@property
|
452
|
-
def state(self):
|
453
|
-
return self.vehicle.refresh_data
|
454
|
-
|
455
|
-
async def turn_on(self):
|
456
|
-
await self.vehicle.set_refresh()
|
457
|
-
await self.vehicle.update()
|
458
|
-
if self.callback is not None:
|
459
|
-
self.callback()
|
460
|
-
|
461
|
-
async def turn_off(self):
|
462
|
-
pass
|
463
|
-
|
464
|
-
@property
|
465
|
-
def assumed_state(self):
|
466
|
-
return False
|
467
|
-
|
468
|
-
@property
|
469
|
-
def attributes(self):
|
470
|
-
return dict(last_result = self.vehicle.refresh_action_status)
|
471
|
-
|
472
|
-
|
473
|
-
class ElectricClimatisation(Switch):
|
474
|
-
def __init__(self):
|
475
|
-
super().__init__(attr="electric_climatisation", name="Electric Climatisation", icon="mdi:radiator")
|
476
|
-
|
477
|
-
@property
|
478
|
-
def state(self):
|
479
|
-
return self.vehicle.electric_climatisation
|
480
|
-
|
481
|
-
async def turn_on(self):
|
482
|
-
await self.vehicle.set_climatisation(mode = 'electric')
|
483
|
-
await self.vehicle.update()
|
484
|
-
|
485
|
-
async def turn_off(self):
|
486
|
-
await self.vehicle.set_climatisation(mode = 'off')
|
487
|
-
await self.vehicle.update()
|
488
|
-
|
489
|
-
@property
|
490
|
-
def assumed_state(self):
|
491
|
-
return False
|
492
|
-
|
493
|
-
@property
|
494
|
-
def attributes(self):
|
495
|
-
attrs = {}
|
496
|
-
if self.vehicle.is_electric_climatisation_attributes_supported:
|
497
|
-
attrs = self.vehicle.electric_climatisation_attributes
|
498
|
-
attrs['last_result'] = self.vehicle.climater_action_status
|
499
|
-
else:
|
500
|
-
attrs['last_result'] = self.vehicle.climater_action_status
|
501
|
-
return attrs
|
502
|
-
|
503
|
-
|
504
|
-
class AuxiliaryClimatisation(Switch):
|
505
|
-
def __init__(self):
|
506
|
-
super().__init__(attr="auxiliary_climatisation", name="Auxiliary Climatisation", icon="mdi:radiator")
|
507
|
-
|
508
|
-
def configurate(self, **config):
|
509
|
-
self.spin = config.get('spin', '')
|
510
|
-
|
511
|
-
@property
|
512
|
-
def state(self):
|
513
|
-
return self.vehicle.auxiliary_climatisation
|
514
|
-
|
515
|
-
async def turn_on(self):
|
516
|
-
await self.vehicle.set_climatisation(mode = 'auxiliary', spin = self.spin)
|
517
|
-
await self.vehicle.update()
|
518
|
-
|
519
|
-
async def turn_off(self):
|
520
|
-
await self.vehicle.set_climatisation(mode = 'off')
|
521
|
-
await self.vehicle.update()
|
522
|
-
|
523
|
-
@property
|
524
|
-
def assumed_state(self):
|
525
|
-
return False
|
526
|
-
|
527
|
-
@property
|
528
|
-
def attributes(self):
|
529
|
-
return dict(last_result = self.vehicle.climater_action_status)
|
530
|
-
|
531
|
-
|
532
|
-
class Charging(Switch):
|
533
|
-
def __init__(self):
|
534
|
-
super().__init__(attr="charging", name="Charging", icon="mdi:battery")
|
535
|
-
|
536
|
-
@property
|
537
|
-
def state(self):
|
538
|
-
return self.vehicle.charging
|
539
|
-
|
540
|
-
async def turn_on(self):
|
541
|
-
await self.vehicle.set_charger('start')
|
542
|
-
await self.vehicle.update()
|
543
|
-
|
544
|
-
async def turn_off(self):
|
545
|
-
await self.vehicle.set_charger('stop')
|
546
|
-
await self.vehicle.update()
|
547
|
-
|
548
|
-
@property
|
549
|
-
def assumed_state(self):
|
550
|
-
return False
|
551
|
-
|
552
|
-
@property
|
553
|
-
def attributes(self):
|
554
|
-
return dict(last_result = self.vehicle.charger_action_status)
|
555
|
-
|
556
|
-
|
557
|
-
class WindowHeater(Switch):
|
558
|
-
def __init__(self):
|
559
|
-
super().__init__(attr="window_heater", name="Window Heater", icon="mdi:car-defrost-rear")
|
560
|
-
|
561
|
-
@property
|
562
|
-
def state(self):
|
563
|
-
return self.vehicle.window_heater
|
564
|
-
|
565
|
-
async def turn_on(self):
|
566
|
-
await self.vehicle.set_window_heating('start')
|
567
|
-
await self.vehicle.update()
|
568
|
-
|
569
|
-
async def turn_off(self):
|
570
|
-
await self.vehicle.set_window_heating('stop')
|
571
|
-
await self.vehicle.update()
|
572
|
-
|
573
|
-
@property
|
574
|
-
def assumed_state(self):
|
575
|
-
return False
|
576
|
-
|
577
|
-
|
578
|
-
@property
|
579
|
-
def attributes(self):
|
580
|
-
return dict(last_result = self.vehicle.climater_action_status)
|
581
|
-
|
582
|
-
|
583
|
-
class SeatHeating(Switch):
|
584
|
-
def __init__(self):
|
585
|
-
super().__init__(attr="seat_heating", name="Seat Heating", icon="mdi:seat-recline-normal")
|
586
|
-
|
587
|
-
@property
|
588
|
-
def state(self):
|
589
|
-
return self.vehicle.seat_heating
|
590
|
-
|
591
|
-
async def turn_on(self):
|
592
|
-
#await self.vehicle.set_seat_heating('start')
|
593
|
-
#await self.vehicle.update()
|
594
|
-
pass
|
595
|
-
|
596
|
-
async def turn_off(self):
|
597
|
-
#await self.vehicle.set_seat_heating('stop')
|
598
|
-
#await self.vehicle.update()
|
599
|
-
pass
|
600
|
-
|
601
|
-
@property
|
602
|
-
def assumed_state(self):
|
603
|
-
return False
|
604
|
-
|
605
|
-
#@property
|
606
|
-
#def attributes(self):
|
607
|
-
# return dict(last_result = self.vehicle.climater_action_status)
|
608
|
-
|
609
|
-
|
610
|
-
class BatteryClimatisation(Switch):
|
611
|
-
def __init__(self):
|
612
|
-
super().__init__(attr="climatisation_without_external_power", name="Climatisation from battery", icon="mdi:power-plug")
|
613
|
-
|
614
|
-
@property
|
615
|
-
def state(self):
|
616
|
-
return self.vehicle.climatisation_without_external_power
|
617
|
-
|
618
|
-
async def turn_on(self):
|
619
|
-
await self.vehicle.set_battery_climatisation(True)
|
620
|
-
await self.vehicle.update()
|
621
|
-
|
622
|
-
async def turn_off(self):
|
623
|
-
await self.vehicle.set_battery_climatisation(False)
|
624
|
-
await self.vehicle.update()
|
625
|
-
|
626
|
-
@property
|
627
|
-
def assumed_state(self):
|
628
|
-
return False
|
629
|
-
|
630
|
-
@property
|
631
|
-
def attributes(self):
|
632
|
-
return dict(last_result = self.vehicle.climater_action_status)
|
633
|
-
|
634
|
-
|
635
|
-
class PHeaterHeating(Switch):
|
636
|
-
def __init__(self):
|
637
|
-
super().__init__(attr="pheater_heating", name="Parking Heater Heating", icon="mdi:radiator")
|
638
|
-
|
639
|
-
def configurate(self, **config):
|
640
|
-
self.spin = config.get('spin', '')
|
641
|
-
self.duration = config.get('combustionengineheatingduration', 30)
|
642
|
-
|
643
|
-
@property
|
644
|
-
def state(self):
|
645
|
-
return self.vehicle.pheater_heating
|
646
|
-
|
647
|
-
async def turn_on(self):
|
648
|
-
await self.vehicle.set_pheater(mode='heating', spin=self.spin)
|
649
|
-
await self.vehicle.update()
|
650
|
-
|
651
|
-
async def turn_off(self):
|
652
|
-
await self.vehicle.set_pheater(mode='off', spin=self.spin)
|
653
|
-
await self.vehicle.update()
|
654
|
-
|
655
|
-
@property
|
656
|
-
def assumed_state(self):
|
657
|
-
return False
|
658
|
-
|
659
|
-
@property
|
660
|
-
def attributes(self):
|
661
|
-
return dict(last_result = self.vehicle.pheater_action_status)
|
662
|
-
|
663
|
-
|
664
|
-
class PHeaterVentilation(Switch):
|
665
|
-
def __init__(self):
|
666
|
-
super().__init__(attr="pheater_ventilation", name="Parking Heater Ventilation", icon="mdi:radiator")
|
667
|
-
|
668
|
-
def configurate(self, **config):
|
669
|
-
self.spin = config.get('spin', '')
|
670
|
-
self.duration = config.get('combustionengineclimatisationduration', 30)
|
671
|
-
|
672
|
-
@property
|
673
|
-
def state(self):
|
674
|
-
return self.vehicle.pheater_ventilation
|
675
|
-
|
676
|
-
async def turn_on(self):
|
677
|
-
await self.vehicle.set_pheater(mode='ventilation', spin=self.spin)
|
678
|
-
await self.vehicle.update()
|
679
|
-
|
680
|
-
async def turn_off(self):
|
681
|
-
await self.vehicle.set_pheater(mode='off', spin=self.spin)
|
682
|
-
await self.vehicle.update()
|
683
|
-
|
684
|
-
@property
|
685
|
-
def assumed_state(self):
|
686
|
-
return False
|
687
|
-
|
688
|
-
@property
|
689
|
-
def attributes(self):
|
690
|
-
return dict(last_result = self.vehicle.pheater_action_status)
|
691
|
-
|
692
|
-
|
693
|
-
class Warnings(Sensor):
|
694
|
-
def __init__(self):
|
695
|
-
super().__init__(attr="warnings", name="Warnings", icon="mdi:alarm-light")
|
696
|
-
|
697
|
-
@property
|
698
|
-
def state(self):
|
699
|
-
return self.vehicle.warnings
|
700
|
-
|
701
|
-
@property
|
702
|
-
def assumed_state(self):
|
703
|
-
return False
|
704
|
-
|
705
|
-
class DepartureTimer1(Switch):
|
706
|
-
def __init__(self):
|
707
|
-
super().__init__(attr="departure1", name="Departure timer 1", icon="mdi:radiator")
|
708
|
-
|
709
|
-
def configurate(self, **config):
|
710
|
-
self.spin = config.get('spin', '')
|
711
|
-
|
712
|
-
@property
|
713
|
-
def state(self):
|
714
|
-
status = self.vehicle.departure1.get("timerProgrammedStatus", "")
|
715
|
-
if status == "programmed":
|
716
|
-
return True
|
717
|
-
else:
|
718
|
-
return False
|
719
|
-
|
720
|
-
async def turn_on(self):
|
721
|
-
await self.vehicle.set_timer_active(id=1, action="on")
|
722
|
-
await self.vehicle.update()
|
723
|
-
|
724
|
-
async def turn_off(self):
|
725
|
-
await self.vehicle.set_timer_active(id=1, action="off")
|
726
|
-
await self.vehicle.update()
|
727
|
-
|
728
|
-
@property
|
729
|
-
def assumed_state(self):
|
730
|
-
return False
|
731
|
-
|
732
|
-
@property
|
733
|
-
def attributes(self):
|
734
|
-
return dict(self.vehicle.departure1)
|
735
|
-
|
736
|
-
|
737
|
-
class DepartureTimer2(Switch):
|
738
|
-
def __init__(self):
|
739
|
-
super().__init__(attr="departure2", name="Departure timer 2", icon="mdi:radiator")
|
740
|
-
|
741
|
-
def configurate(self, **config):
|
742
|
-
self.spin = config.get('spin', '')
|
743
|
-
|
744
|
-
@property
|
745
|
-
def state(self):
|
746
|
-
status = self.vehicle.departure2.get("timerProgrammedStatus", "")
|
747
|
-
if status == "programmed":
|
748
|
-
return True
|
749
|
-
else:
|
750
|
-
return False
|
751
|
-
|
752
|
-
async def turn_on(self):
|
753
|
-
await self.vehicle.set_timer_active(id=2, action="on")
|
754
|
-
await self.vehicle.update()
|
755
|
-
|
756
|
-
async def turn_off(self):
|
757
|
-
await self.vehicle.set_timer_active(id=2, action="off")
|
758
|
-
await self.vehicle.update()
|
759
|
-
|
760
|
-
@property
|
761
|
-
def assumed_state(self):
|
762
|
-
return False
|
763
|
-
|
764
|
-
@property
|
765
|
-
def attributes(self):
|
766
|
-
return dict(self.vehicle.departure2)
|
767
|
-
|
768
|
-
class DepartureTimer3(Switch):
|
769
|
-
def __init__(self):
|
770
|
-
super().__init__(attr="departure3", name="Departure timer 3", icon="mdi:radiator")
|
771
|
-
|
772
|
-
def configurate(self, **config):
|
773
|
-
self.spin = config.get('spin', '')
|
774
|
-
|
775
|
-
@property
|
776
|
-
def state(self):
|
777
|
-
status = self.vehicle.departure3.get("timerProgrammedStatus", "")
|
778
|
-
if status == "programmed":
|
779
|
-
return True
|
780
|
-
else:
|
781
|
-
return False
|
782
|
-
|
783
|
-
async def turn_on(self):
|
784
|
-
await self.vehicle.set_timer_active(id=3, action="on")
|
785
|
-
await self.vehicle.update()
|
786
|
-
|
787
|
-
async def turn_off(self):
|
788
|
-
await self.vehicle.set_timer_active(id=3, action="off")
|
789
|
-
await self.vehicle.update()
|
790
|
-
|
791
|
-
@property
|
792
|
-
def assumed_state(self):
|
793
|
-
return False
|
794
|
-
|
795
|
-
@property
|
796
|
-
def attributes(self):
|
797
|
-
return dict(self.vehicle.departure3)
|
798
|
-
|
799
|
-
|
800
|
-
class RequestResults(Sensor):
|
801
|
-
def __init__(self):
|
802
|
-
super().__init__(attr="request_results", name="Request results", icon="mdi:chat-alert", unit=None)
|
803
|
-
|
804
|
-
@property
|
805
|
-
def state(self):
|
806
|
-
if self.vehicle.request_results.get('state', False):
|
807
|
-
return self.vehicle.request_results.get('state')
|
808
|
-
return 'N/A'
|
809
|
-
|
810
|
-
@property
|
811
|
-
def assumed_state(self):
|
812
|
-
return False
|
813
|
-
|
814
|
-
@property
|
815
|
-
def attributes(self):
|
816
|
-
return dict(self.vehicle.request_results)
|
817
|
-
|
818
|
-
|
819
|
-
def create_instruments():
|
820
|
-
return [
|
821
|
-
Position(),
|
822
|
-
DoorLock(),
|
823
|
-
TrunkLock(),
|
824
|
-
RequestFlash(),
|
825
|
-
RequestHonkAndFlash(),
|
826
|
-
RequestUpdate(),
|
827
|
-
WindowHeater(),
|
828
|
-
BatteryClimatisation(),
|
829
|
-
ElectricClimatisation(),
|
830
|
-
AuxiliaryClimatisation(),
|
831
|
-
PHeaterVentilation(),
|
832
|
-
PHeaterHeating(),
|
833
|
-
#ElectricClimatisationClimate(),
|
834
|
-
#CombustionClimatisationClimate(),
|
835
|
-
Charging(),
|
836
|
-
Warnings(),
|
837
|
-
RequestResults(),
|
838
|
-
DepartureTimer1(),
|
839
|
-
DepartureTimer2(),
|
840
|
-
DepartureTimer3(),
|
841
|
-
Sensor(
|
842
|
-
attr="distance",
|
843
|
-
name="Odometer",
|
844
|
-
icon="mdi:speedometer",
|
845
|
-
unit="km",
|
846
|
-
|
847
|
-
|
848
|
-
|
849
|
-
|
850
|
-
|
851
|
-
|
852
|
-
|
853
|
-
|
854
|
-
|
855
|
-
|
856
|
-
|
857
|
-
|
858
|
-
|
859
|
-
|
860
|
-
|
861
|
-
|
862
|
-
|
863
|
-
|
864
|
-
|
865
|
-
|
866
|
-
|
867
|
-
|
868
|
-
|
869
|
-
|
870
|
-
|
871
|
-
|
872
|
-
|
873
|
-
|
874
|
-
|
875
|
-
|
876
|
-
|
877
|
-
|
878
|
-
|
879
|
-
|
880
|
-
|
881
|
-
|
882
|
-
|
883
|
-
|
884
|
-
|
885
|
-
|
886
|
-
|
887
|
-
|
888
|
-
|
889
|
-
|
890
|
-
|
891
|
-
|
892
|
-
|
893
|
-
|
894
|
-
|
895
|
-
|
896
|
-
|
897
|
-
|
898
|
-
|
899
|
-
|
900
|
-
|
901
|
-
|
902
|
-
|
903
|
-
|
904
|
-
|
905
|
-
|
906
|
-
|
907
|
-
|
908
|
-
|
909
|
-
|
910
|
-
|
911
|
-
|
912
|
-
|
913
|
-
|
914
|
-
|
915
|
-
|
916
|
-
|
917
|
-
|
918
|
-
|
919
|
-
|
920
|
-
|
921
|
-
|
922
|
-
|
923
|
-
|
924
|
-
|
925
|
-
|
926
|
-
|
927
|
-
|
928
|
-
|
929
|
-
|
930
|
-
|
931
|
-
|
932
|
-
|
933
|
-
),
|
934
|
-
Sensor(
|
935
|
-
attr="
|
936
|
-
name="
|
937
|
-
icon="mdi:car",
|
938
|
-
unit="km",
|
939
|
-
|
940
|
-
|
941
|
-
|
942
|
-
|
943
|
-
|
944
|
-
|
945
|
-
|
946
|
-
|
947
|
-
|
948
|
-
|
949
|
-
|
950
|
-
|
951
|
-
|
952
|
-
|
953
|
-
|
954
|
-
|
955
|
-
|
956
|
-
|
957
|
-
|
958
|
-
|
959
|
-
|
960
|
-
|
961
|
-
|
962
|
-
|
963
|
-
|
964
|
-
|
965
|
-
|
966
|
-
|
967
|
-
|
968
|
-
|
969
|
-
|
970
|
-
|
971
|
-
|
972
|
-
|
973
|
-
|
974
|
-
|
975
|
-
|
976
|
-
|
977
|
-
|
978
|
-
|
979
|
-
|
980
|
-
|
981
|
-
|
982
|
-
|
983
|
-
|
984
|
-
|
985
|
-
|
986
|
-
|
987
|
-
|
988
|
-
|
989
|
-
),
|
990
|
-
Sensor(
|
991
|
-
attr="
|
992
|
-
name="Last trip
|
993
|
-
icon="mdi:
|
994
|
-
unit="
|
995
|
-
),
|
996
|
-
Sensor(
|
997
|
-
attr="
|
998
|
-
name="Last trip
|
999
|
-
icon="mdi:
|
1000
|
-
unit="
|
1001
|
-
|
1002
|
-
|
1003
|
-
|
1004
|
-
|
1005
|
-
|
1006
|
-
|
1007
|
-
|
1008
|
-
|
1009
|
-
|
1010
|
-
|
1011
|
-
|
1012
|
-
|
1013
|
-
|
1014
|
-
|
1015
|
-
|
1016
|
-
|
1017
|
-
|
1018
|
-
|
1019
|
-
|
1020
|
-
|
1021
|
-
|
1022
|
-
|
1023
|
-
|
1024
|
-
|
1025
|
-
|
1026
|
-
|
1027
|
-
|
1028
|
-
|
1029
|
-
|
1030
|
-
|
1031
|
-
|
1032
|
-
|
1033
|
-
|
1034
|
-
|
1035
|
-
|
1036
|
-
|
1037
|
-
),
|
1038
|
-
Sensor(
|
1039
|
-
attr="
|
1040
|
-
name="Last
|
1041
|
-
icon="mdi:
|
1042
|
-
unit="
|
1043
|
-
|
1044
|
-
|
1045
|
-
|
1046
|
-
|
1047
|
-
|
1048
|
-
|
1049
|
-
|
1050
|
-
|
1051
|
-
|
1052
|
-
|
1053
|
-
|
1054
|
-
|
1055
|
-
|
1056
|
-
|
1057
|
-
|
1058
|
-
|
1059
|
-
|
1060
|
-
|
1061
|
-
|
1062
|
-
|
1063
|
-
|
1064
|
-
|
1065
|
-
|
1066
|
-
|
1067
|
-
|
1068
|
-
|
1069
|
-
|
1070
|
-
|
1071
|
-
|
1072
|
-
|
1073
|
-
|
1074
|
-
|
1075
|
-
|
1076
|
-
|
1077
|
-
|
1078
|
-
|
1079
|
-
|
1080
|
-
|
1081
|
-
|
1082
|
-
|
1083
|
-
|
1084
|
-
|
1085
|
-
),
|
1086
|
-
Sensor(
|
1087
|
-
attr="
|
1088
|
-
name="
|
1089
|
-
icon="mdi:
|
1090
|
-
|
1091
|
-
|
1092
|
-
|
1093
|
-
|
1094
|
-
|
1095
|
-
|
1096
|
-
|
1097
|
-
|
1098
|
-
|
1099
|
-
|
1100
|
-
|
1101
|
-
|
1102
|
-
|
1103
|
-
|
1104
|
-
|
1105
|
-
|
1106
|
-
),
|
1107
|
-
Sensor(
|
1108
|
-
attr="
|
1109
|
-
name="
|
1110
|
-
icon="mdi:
|
1111
|
-
unit="
|
1112
|
-
device_class="
|
1113
|
-
),
|
1114
|
-
Sensor(
|
1115
|
-
attr="
|
1116
|
-
name="
|
1117
|
-
icon="mdi:
|
1118
|
-
|
1119
|
-
|
1120
|
-
|
1121
|
-
|
1122
|
-
|
1123
|
-
|
1124
|
-
|
1125
|
-
|
1126
|
-
|
1127
|
-
|
1128
|
-
|
1129
|
-
|
1130
|
-
|
1131
|
-
|
1132
|
-
|
1133
|
-
|
1134
|
-
|
1135
|
-
),
|
1136
|
-
|
1137
|
-
attr="
|
1138
|
-
name="
|
1139
|
-
|
1140
|
-
|
1141
|
-
|
1142
|
-
|
1143
|
-
|
1144
|
-
|
1145
|
-
|
1146
|
-
|
1147
|
-
|
1148
|
-
),
|
1149
|
-
BinarySensor(
|
1150
|
-
attr="
|
1151
|
-
name="
|
1152
|
-
device_class="
|
1153
|
-
|
1154
|
-
|
1155
|
-
|
1156
|
-
|
1157
|
-
|
1158
|
-
|
1159
|
-
|
1160
|
-
|
1161
|
-
|
1162
|
-
|
1163
|
-
|
1164
|
-
|
1165
|
-
|
1166
|
-
|
1167
|
-
|
1168
|
-
|
1169
|
-
|
1170
|
-
|
1171
|
-
|
1172
|
-
|
1173
|
-
|
1174
|
-
|
1175
|
-
|
1176
|
-
|
1177
|
-
|
1178
|
-
|
1179
|
-
|
1180
|
-
|
1181
|
-
|
1182
|
-
|
1183
|
-
|
1184
|
-
|
1185
|
-
|
1186
|
-
|
1187
|
-
|
1188
|
-
|
1189
|
-
|
1190
|
-
|
1191
|
-
|
1192
|
-
|
1193
|
-
|
1194
|
-
|
1195
|
-
|
1196
|
-
|
1197
|
-
|
1198
|
-
|
1199
|
-
|
1200
|
-
|
1201
|
-
|
1202
|
-
|
1203
|
-
|
1204
|
-
|
1205
|
-
|
1206
|
-
|
1207
|
-
|
1208
|
-
|
1209
|
-
|
1210
|
-
|
1211
|
-
|
1212
|
-
|
1213
|
-
|
1214
|
-
|
1215
|
-
|
1216
|
-
|
1217
|
-
|
1218
|
-
|
1219
|
-
|
1220
|
-
|
1221
|
-
|
1222
|
-
|
1223
|
-
|
1224
|
-
|
1225
|
-
|
1226
|
-
|
1227
|
-
|
1228
|
-
|
1229
|
-
|
1230
|
-
|
1231
|
-
|
1232
|
-
|
1233
|
-
|
1234
|
-
|
1235
|
-
|
1236
|
-
|
1237
|
-
|
1238
|
-
|
1239
|
-
|
1240
|
-
),
|
1241
|
-
BinarySensor(
|
1242
|
-
attr="
|
1243
|
-
name="
|
1244
|
-
device_class="
|
1245
|
-
|
1246
|
-
|
1247
|
-
|
1248
|
-
|
1249
|
-
|
1250
|
-
|
1251
|
-
|
1252
|
-
|
1253
|
-
|
1254
|
-
|
1255
|
-
|
1256
|
-
|
1257
|
-
|
1258
|
-
|
1
|
+
# Utilities for integration with Home Assistant
|
2
|
+
# Thanks to molobrakos and Farfar
|
3
|
+
|
4
|
+
import logging
|
5
|
+
from datetime import datetime
|
6
|
+
from .utilities import camel2slug
|
7
|
+
|
8
|
+
_LOGGER = logging.getLogger(__name__)
|
9
|
+
|
10
|
+
class Instrument:
|
11
|
+
def __init__(self, component, attr, name, icon=None):
|
12
|
+
self.attr = attr
|
13
|
+
self.component = component
|
14
|
+
self.name = name
|
15
|
+
self.vehicle = None
|
16
|
+
self.icon = icon
|
17
|
+
self.callback = None
|
18
|
+
|
19
|
+
def __repr__(self):
|
20
|
+
return self.full_name
|
21
|
+
|
22
|
+
def configurate(self, **args):
|
23
|
+
pass
|
24
|
+
|
25
|
+
@property
|
26
|
+
def slug_attr(self):
|
27
|
+
return camel2slug(self.attr.replace(".", "_"))
|
28
|
+
|
29
|
+
def setup(self, vehicle, **config):
|
30
|
+
self.vehicle = vehicle
|
31
|
+
if not self.is_supported:
|
32
|
+
return False
|
33
|
+
|
34
|
+
self.configurate(**config)
|
35
|
+
return True
|
36
|
+
|
37
|
+
@property
|
38
|
+
def vehicle_name(self):
|
39
|
+
return self.vehicle.vin
|
40
|
+
|
41
|
+
@property
|
42
|
+
def full_name(self):
|
43
|
+
return f"{self.vehicle_name} {self.name}"
|
44
|
+
|
45
|
+
@property
|
46
|
+
def is_mutable(self):
|
47
|
+
raise NotImplementedError("Must be set")
|
48
|
+
|
49
|
+
@property
|
50
|
+
def str_state(self):
|
51
|
+
return self.state
|
52
|
+
|
53
|
+
@property
|
54
|
+
def state(self):
|
55
|
+
if hasattr(self.vehicle, self.attr):
|
56
|
+
return getattr(self.vehicle, self.attr)
|
57
|
+
else:
|
58
|
+
_LOGGER.debug(f'Could not find attribute "{self.attr}"')
|
59
|
+
return self.vehicle.get_attr(self.attr)
|
60
|
+
|
61
|
+
@property
|
62
|
+
def attributes(self):
|
63
|
+
return {}
|
64
|
+
|
65
|
+
@property
|
66
|
+
def is_supported(self):
|
67
|
+
supported = 'is_' + self.attr + "_supported"
|
68
|
+
if hasattr(self.vehicle, supported):
|
69
|
+
return getattr(self.vehicle, supported)
|
70
|
+
else:
|
71
|
+
return False
|
72
|
+
|
73
|
+
|
74
|
+
class Sensor(Instrument):
|
75
|
+
def __init__(self, attr, name, icon, unit=None, device_class=None):
|
76
|
+
super().__init__(component="sensor", attr=attr, name=name, icon=icon)
|
77
|
+
self.device_class = device_class
|
78
|
+
self.unit = unit
|
79
|
+
self.convert = False
|
80
|
+
|
81
|
+
def configurate(self, **config):
|
82
|
+
if self.unit and config.get('miles', False) is True:
|
83
|
+
if "km" == self.unit:
|
84
|
+
self.unit = "mi"
|
85
|
+
self.convert = True
|
86
|
+
elif "km/h" == self.unit:
|
87
|
+
self.unit = "mi/h"
|
88
|
+
self.convert = True
|
89
|
+
elif "l/100km" == self.unit:
|
90
|
+
self.unit = "l/100 mi"
|
91
|
+
self.convert = True
|
92
|
+
elif "kWh/100km" == self.unit:
|
93
|
+
self.unit = "kWh/100 mi"
|
94
|
+
self.convert = True
|
95
|
+
elif self.unit and config.get('scandinavian_miles', False) is True:
|
96
|
+
if "km" == self.unit:
|
97
|
+
self.unit = "mil"
|
98
|
+
elif "km/h" == self.unit:
|
99
|
+
self.unit = "mil/h"
|
100
|
+
elif "l/100km" == self.unit:
|
101
|
+
self.unit = "l/100 mil"
|
102
|
+
elif "kWh/100km" == self.unit:
|
103
|
+
self.unit = "kWh/100 mil"
|
104
|
+
|
105
|
+
# Init placeholder for parking heater duration
|
106
|
+
config.get('parkingheater', 30)
|
107
|
+
if "pheater_duration" == self.attr:
|
108
|
+
setValue = config.get('climatisation_duration', 30)
|
109
|
+
self.vehicle.pheater_duration = setValue
|
110
|
+
|
111
|
+
@property
|
112
|
+
def is_mutable(self):
|
113
|
+
return False
|
114
|
+
|
115
|
+
@property
|
116
|
+
def str_state(self):
|
117
|
+
if self.unit:
|
118
|
+
return f'{self.state} {self.unit}'
|
119
|
+
else:
|
120
|
+
return f'{self.state}'
|
121
|
+
|
122
|
+
@property
|
123
|
+
def state(self):
|
124
|
+
val = super().state
|
125
|
+
# Convert to miles
|
126
|
+
if val and self.unit and "mi" in self.unit and self.convert is True:
|
127
|
+
return int(round(val / 1.609344))
|
128
|
+
elif val and self.unit and "mi/h" in self.unit and self.convert is True:
|
129
|
+
return int(round(val / 1.609344))
|
130
|
+
elif val and self.unit and "gal/100 mi" in self.unit and self.convert is True:
|
131
|
+
return round(val * 0.4251438, 1)
|
132
|
+
elif val and self.unit and "kWh/100 mi" in self.unit and self.convert is True:
|
133
|
+
return round(val * 0.4251438, 1)
|
134
|
+
elif val and self.unit and "°F" in self.unit and self.convert is True:
|
135
|
+
temp = round((val * 9 / 5) + 32, 1)
|
136
|
+
return temp
|
137
|
+
elif val and self.unit in ['mil', 'mil/h']:
|
138
|
+
return val / 10
|
139
|
+
else:
|
140
|
+
return val
|
141
|
+
|
142
|
+
|
143
|
+
class BinarySensor(Instrument):
|
144
|
+
def __init__(self, attr, name, device_class, icon='', reverse_state=False):
|
145
|
+
super().__init__(component="binary_sensor", attr=attr, name=name, icon=icon)
|
146
|
+
self.device_class = device_class
|
147
|
+
self.reverse_state = reverse_state
|
148
|
+
|
149
|
+
@property
|
150
|
+
def is_mutable(self):
|
151
|
+
return False
|
152
|
+
|
153
|
+
@property
|
154
|
+
def str_state(self):
|
155
|
+
if self.device_class in ["door", "window"]:
|
156
|
+
return "Closed" if self.state else "Open"
|
157
|
+
if self.device_class == "lock":
|
158
|
+
return "Locked" if self.state else "Unlocked"
|
159
|
+
if self.device_class == "safety":
|
160
|
+
return "Warning!" if self.state else "OK"
|
161
|
+
if self.device_class == "plug":
|
162
|
+
return "Connected" if self.state else "Disconnected"
|
163
|
+
if self.state is None:
|
164
|
+
_LOGGER.error(f"Can not encode state {self.attr} {self.state}")
|
165
|
+
return "?"
|
166
|
+
return "On" if self.state else "Off"
|
167
|
+
|
168
|
+
@property
|
169
|
+
def state(self):
|
170
|
+
val = super().state
|
171
|
+
|
172
|
+
if isinstance(val, (bool, list)):
|
173
|
+
if self.reverse_state:
|
174
|
+
if bool(val):
|
175
|
+
return False
|
176
|
+
else:
|
177
|
+
return True
|
178
|
+
else:
|
179
|
+
return bool(val)
|
180
|
+
elif isinstance(val, str):
|
181
|
+
return val != "Normal"
|
182
|
+
return val
|
183
|
+
|
184
|
+
@property
|
185
|
+
def is_on(self):
|
186
|
+
return self.state
|
187
|
+
|
188
|
+
|
189
|
+
class Switch(Instrument):
|
190
|
+
def __init__(self, attr, name, icon):
|
191
|
+
super().__init__(component="switch", attr=attr, name=name, icon=icon)
|
192
|
+
|
193
|
+
@property
|
194
|
+
def is_mutable(self):
|
195
|
+
return True
|
196
|
+
|
197
|
+
@property
|
198
|
+
def str_state(self):
|
199
|
+
return "On" if self.state else "Off"
|
200
|
+
|
201
|
+
def is_on(self):
|
202
|
+
return self.state
|
203
|
+
|
204
|
+
def turn_on(self):
|
205
|
+
pass
|
206
|
+
|
207
|
+
def turn_off(self):
|
208
|
+
pass
|
209
|
+
|
210
|
+
@property
|
211
|
+
def assumed_state(self):
|
212
|
+
return True
|
213
|
+
|
214
|
+
|
215
|
+
class Climate(Instrument):
|
216
|
+
def __init__(self, attr, name, icon):
|
217
|
+
super().__init__(component="climate", attr=attr, name=name, icon=icon)
|
218
|
+
|
219
|
+
@property
|
220
|
+
def hvac_mode(self):
|
221
|
+
pass
|
222
|
+
|
223
|
+
@property
|
224
|
+
def target_temperature(self):
|
225
|
+
pass
|
226
|
+
|
227
|
+
def set_temperature(self, **kwargs):
|
228
|
+
pass
|
229
|
+
|
230
|
+
def set_hvac_mode(self, hvac_mode):
|
231
|
+
pass
|
232
|
+
|
233
|
+
|
234
|
+
class ElectricClimatisationClimate(Climate):
|
235
|
+
def __init__(self):
|
236
|
+
super().__init__(attr="electric_climatisation", name="Electric Climatisation", icon="mdi:radiator")
|
237
|
+
|
238
|
+
@property
|
239
|
+
def hvac_mode(self):
|
240
|
+
return self.vehicle.electric_climatisation
|
241
|
+
|
242
|
+
@property
|
243
|
+
def target_temperature(self):
|
244
|
+
return self.vehicle.climatisation_target_temperature
|
245
|
+
|
246
|
+
async def set_temperature(self, temperature):
|
247
|
+
await self.vehicle.climatisation_target(temperature)
|
248
|
+
|
249
|
+
async def set_hvac_mode(self, hvac_mode):
|
250
|
+
if hvac_mode:
|
251
|
+
await self.vehicle.climatisation('electric')
|
252
|
+
else:
|
253
|
+
await self.vehicle.climatisation('off')
|
254
|
+
|
255
|
+
|
256
|
+
class CombustionClimatisationClimate(Climate):
|
257
|
+
def __init__(self):
|
258
|
+
super().__init__(attr="pheater_heating", name="Parking Heater Climatisation", icon="mdi:radiator")
|
259
|
+
|
260
|
+
def configurate(self, **config):
|
261
|
+
self.spin = config.get('spin', '')
|
262
|
+
self.duration = config.get('combustionengineheatingduration', 30)
|
263
|
+
|
264
|
+
@property
|
265
|
+
def hvac_mode(self):
|
266
|
+
return self.vehicle.pheater_heating
|
267
|
+
|
268
|
+
@property
|
269
|
+
def target_temperature(self):
|
270
|
+
return self.vehicle.climatisation_target_temperature
|
271
|
+
|
272
|
+
async def set_temperature(self, temperature):
|
273
|
+
await self.vehicle.setClimatisationTargetTemperature(temperature)
|
274
|
+
|
275
|
+
async def set_hvac_mode(self, hvac_mode):
|
276
|
+
if hvac_mode:
|
277
|
+
await self.vehicle.pheater_climatisation(spin=self.spin, duration=self.duration, mode='heating')
|
278
|
+
else:
|
279
|
+
await self.vehicle.pheater_climatisation(spin=self.spin, mode='off')
|
280
|
+
|
281
|
+
|
282
|
+
class Position(Instrument):
|
283
|
+
def __init__(self):
|
284
|
+
super().__init__(component="device_tracker", attr="position", name="Position")
|
285
|
+
|
286
|
+
@property
|
287
|
+
def is_mutable(self):
|
288
|
+
return False
|
289
|
+
|
290
|
+
@property
|
291
|
+
def state(self):
|
292
|
+
state = super().state #or {}
|
293
|
+
return (
|
294
|
+
state.get("lat", "?"),
|
295
|
+
state.get("lng", "?"),
|
296
|
+
state.get("address", "?"),
|
297
|
+
state.get("timestamp", None),
|
298
|
+
)
|
299
|
+
|
300
|
+
@property
|
301
|
+
def str_state(self):
|
302
|
+
state = super().state #or {}
|
303
|
+
ts = state.get("timestamp", None)
|
304
|
+
if isinstance(ts, str):
|
305
|
+
time = str(datetime.strptime(ts,'%Y-%m-%dT%H:%M:%SZ').astimezone(tz=None))
|
306
|
+
elif isinstance(ts, datetime):
|
307
|
+
time = str(ts.astimezone(tz=None))
|
308
|
+
else:
|
309
|
+
time = None
|
310
|
+
return (
|
311
|
+
state.get("lat", "?"),
|
312
|
+
state.get("lng", "?"),
|
313
|
+
state.get("address", "?"),
|
314
|
+
time,
|
315
|
+
)
|
316
|
+
|
317
|
+
|
318
|
+
class DoorLock(Instrument):
|
319
|
+
def __init__(self):
|
320
|
+
super().__init__(component="lock", attr="door_locked", name="Door locked")
|
321
|
+
|
322
|
+
def configurate(self, **config):
|
323
|
+
self.spin = config.get('spin', '')
|
324
|
+
|
325
|
+
@property
|
326
|
+
def is_mutable(self):
|
327
|
+
return True
|
328
|
+
|
329
|
+
@property
|
330
|
+
def str_state(self):
|
331
|
+
return "Locked" if self.state else "Unlocked"
|
332
|
+
|
333
|
+
@property
|
334
|
+
def state(self):
|
335
|
+
return self.vehicle.door_locked
|
336
|
+
|
337
|
+
@property
|
338
|
+
def is_locked(self):
|
339
|
+
return self.state
|
340
|
+
|
341
|
+
async def lock(self):
|
342
|
+
try:
|
343
|
+
response = await self.vehicle.set_lock('lock', self.spin)
|
344
|
+
await self.vehicle.update()
|
345
|
+
if self.callback is not None:
|
346
|
+
self.callback()
|
347
|
+
return response
|
348
|
+
except Exception as e:
|
349
|
+
_LOGGER.error(f"Lock failed: {e}")
|
350
|
+
return False
|
351
|
+
|
352
|
+
async def unlock(self):
|
353
|
+
try:
|
354
|
+
response = await self.vehicle.set_lock('unlock', self.spin)
|
355
|
+
await self.vehicle.update()
|
356
|
+
if self.callback is not None:
|
357
|
+
self.callback()
|
358
|
+
return response
|
359
|
+
except Exception as e:
|
360
|
+
_LOGGER.error(f"Unlock failed: {e}")
|
361
|
+
return False
|
362
|
+
|
363
|
+
@property
|
364
|
+
def attributes(self):
|
365
|
+
return dict(last_result = self.vehicle.lock_action_status)
|
366
|
+
|
367
|
+
|
368
|
+
class TrunkLock(Instrument):
|
369
|
+
def __init__(self):
|
370
|
+
super().__init__(component="lock", attr="trunk_locked", name="Trunk locked")
|
371
|
+
|
372
|
+
@property
|
373
|
+
def is_mutable(self):
|
374
|
+
return True
|
375
|
+
|
376
|
+
@property
|
377
|
+
def str_state(self):
|
378
|
+
return "Locked" if self.state else "Unlocked"
|
379
|
+
|
380
|
+
@property
|
381
|
+
def state(self):
|
382
|
+
return self.vehicle.trunk_locked
|
383
|
+
|
384
|
+
@property
|
385
|
+
def is_locked(self):
|
386
|
+
return self.state
|
387
|
+
|
388
|
+
async def lock(self):
|
389
|
+
return None
|
390
|
+
|
391
|
+
async def unlock(self):
|
392
|
+
return None
|
393
|
+
|
394
|
+
# Switches
|
395
|
+
class RequestHonkAndFlash(Switch):
|
396
|
+
def __init__(self):
|
397
|
+
super().__init__(attr="request_honkandflash", name="Start honking and flashing", icon="mdi:car-emergency")
|
398
|
+
|
399
|
+
@property
|
400
|
+
def state(self):
|
401
|
+
return self.vehicle.request_honkandflash
|
402
|
+
|
403
|
+
async def turn_on(self):
|
404
|
+
await self.vehicle.set_honkandflash('honkandflash')
|
405
|
+
await self.vehicle.update()
|
406
|
+
if self.callback is not None:
|
407
|
+
self.callback()
|
408
|
+
|
409
|
+
async def turn_off(self):
|
410
|
+
pass
|
411
|
+
|
412
|
+
@property
|
413
|
+
def assumed_state(self):
|
414
|
+
return False
|
415
|
+
|
416
|
+
@property
|
417
|
+
def attributes(self):
|
418
|
+
return dict(last_result = self.vehicle.honkandflash_action_status)
|
419
|
+
|
420
|
+
|
421
|
+
class RequestFlash(Switch):
|
422
|
+
def __init__(self):
|
423
|
+
super().__init__(attr="request_flash", name="Start flashing", icon="mdi:car-parking-lights")
|
424
|
+
|
425
|
+
@property
|
426
|
+
def state(self):
|
427
|
+
return self.vehicle.request_flash
|
428
|
+
|
429
|
+
async def turn_on(self):
|
430
|
+
await self.vehicle.set_honkandflash('flash')
|
431
|
+
await self.vehicle.update()
|
432
|
+
if self.callback is not None:
|
433
|
+
self.callback()
|
434
|
+
|
435
|
+
async def turn_off(self):
|
436
|
+
pass
|
437
|
+
|
438
|
+
@property
|
439
|
+
def assumed_state(self):
|
440
|
+
return False
|
441
|
+
|
442
|
+
@property
|
443
|
+
def attributes(self):
|
444
|
+
return dict(last_result = self.vehicle.honkandflash_action_status)
|
445
|
+
|
446
|
+
|
447
|
+
class RequestUpdate(Switch):
|
448
|
+
def __init__(self):
|
449
|
+
super().__init__(attr="refresh_data", name="Force data refresh", icon="mdi:car-connected")
|
450
|
+
|
451
|
+
@property
|
452
|
+
def state(self):
|
453
|
+
return self.vehicle.refresh_data
|
454
|
+
|
455
|
+
async def turn_on(self):
|
456
|
+
await self.vehicle.set_refresh()
|
457
|
+
await self.vehicle.update()
|
458
|
+
if self.callback is not None:
|
459
|
+
self.callback()
|
460
|
+
|
461
|
+
async def turn_off(self):
|
462
|
+
pass
|
463
|
+
|
464
|
+
@property
|
465
|
+
def assumed_state(self):
|
466
|
+
return False
|
467
|
+
|
468
|
+
@property
|
469
|
+
def attributes(self):
|
470
|
+
return dict(last_result = self.vehicle.refresh_action_status)
|
471
|
+
|
472
|
+
|
473
|
+
class ElectricClimatisation(Switch):
|
474
|
+
def __init__(self):
|
475
|
+
super().__init__(attr="electric_climatisation", name="Electric Climatisation", icon="mdi:radiator")
|
476
|
+
|
477
|
+
@property
|
478
|
+
def state(self):
|
479
|
+
return self.vehicle.electric_climatisation
|
480
|
+
|
481
|
+
async def turn_on(self):
|
482
|
+
await self.vehicle.set_climatisation(mode = 'electric')
|
483
|
+
await self.vehicle.update()
|
484
|
+
|
485
|
+
async def turn_off(self):
|
486
|
+
await self.vehicle.set_climatisation(mode = 'off')
|
487
|
+
await self.vehicle.update()
|
488
|
+
|
489
|
+
@property
|
490
|
+
def assumed_state(self):
|
491
|
+
return False
|
492
|
+
|
493
|
+
@property
|
494
|
+
def attributes(self):
|
495
|
+
attrs = {}
|
496
|
+
if self.vehicle.is_electric_climatisation_attributes_supported:
|
497
|
+
attrs = self.vehicle.electric_climatisation_attributes
|
498
|
+
attrs['last_result'] = self.vehicle.climater_action_status
|
499
|
+
else:
|
500
|
+
attrs['last_result'] = self.vehicle.climater_action_status
|
501
|
+
return attrs
|
502
|
+
|
503
|
+
|
504
|
+
class AuxiliaryClimatisation(Switch):
|
505
|
+
def __init__(self):
|
506
|
+
super().__init__(attr="auxiliary_climatisation", name="Auxiliary Climatisation", icon="mdi:radiator")
|
507
|
+
|
508
|
+
def configurate(self, **config):
|
509
|
+
self.spin = config.get('spin', '')
|
510
|
+
|
511
|
+
@property
|
512
|
+
def state(self):
|
513
|
+
return self.vehicle.auxiliary_climatisation
|
514
|
+
|
515
|
+
async def turn_on(self):
|
516
|
+
await self.vehicle.set_climatisation(mode = 'auxiliary', spin = self.spin)
|
517
|
+
await self.vehicle.update()
|
518
|
+
|
519
|
+
async def turn_off(self):
|
520
|
+
await self.vehicle.set_climatisation(mode = 'off')
|
521
|
+
await self.vehicle.update()
|
522
|
+
|
523
|
+
@property
|
524
|
+
def assumed_state(self):
|
525
|
+
return False
|
526
|
+
|
527
|
+
@property
|
528
|
+
def attributes(self):
|
529
|
+
return dict(last_result = self.vehicle.climater_action_status)
|
530
|
+
|
531
|
+
|
532
|
+
class Charging(Switch):
|
533
|
+
def __init__(self):
|
534
|
+
super().__init__(attr="charging", name="Charging", icon="mdi:battery")
|
535
|
+
|
536
|
+
@property
|
537
|
+
def state(self):
|
538
|
+
return self.vehicle.charging
|
539
|
+
|
540
|
+
async def turn_on(self):
|
541
|
+
await self.vehicle.set_charger('start')
|
542
|
+
await self.vehicle.update()
|
543
|
+
|
544
|
+
async def turn_off(self):
|
545
|
+
await self.vehicle.set_charger('stop')
|
546
|
+
await self.vehicle.update()
|
547
|
+
|
548
|
+
@property
|
549
|
+
def assumed_state(self):
|
550
|
+
return False
|
551
|
+
|
552
|
+
@property
|
553
|
+
def attributes(self):
|
554
|
+
return dict(last_result = self.vehicle.charger_action_status)
|
555
|
+
|
556
|
+
|
557
|
+
class WindowHeater(Switch):
|
558
|
+
def __init__(self):
|
559
|
+
super().__init__(attr="window_heater", name="Window Heater", icon="mdi:car-defrost-rear")
|
560
|
+
|
561
|
+
@property
|
562
|
+
def state(self):
|
563
|
+
return self.vehicle.window_heater
|
564
|
+
|
565
|
+
async def turn_on(self):
|
566
|
+
await self.vehicle.set_window_heating('start')
|
567
|
+
await self.vehicle.update()
|
568
|
+
|
569
|
+
async def turn_off(self):
|
570
|
+
await self.vehicle.set_window_heating('stop')
|
571
|
+
await self.vehicle.update()
|
572
|
+
|
573
|
+
@property
|
574
|
+
def assumed_state(self):
|
575
|
+
return False
|
576
|
+
|
577
|
+
|
578
|
+
@property
|
579
|
+
def attributes(self):
|
580
|
+
return dict(last_result = self.vehicle.climater_action_status)
|
581
|
+
|
582
|
+
|
583
|
+
class SeatHeating(Switch):
|
584
|
+
def __init__(self):
|
585
|
+
super().__init__(attr="seat_heating", name="Seat Heating", icon="mdi:seat-recline-normal")
|
586
|
+
|
587
|
+
@property
|
588
|
+
def state(self):
|
589
|
+
return self.vehicle.seat_heating
|
590
|
+
|
591
|
+
async def turn_on(self):
|
592
|
+
#await self.vehicle.set_seat_heating('start')
|
593
|
+
#await self.vehicle.update()
|
594
|
+
pass
|
595
|
+
|
596
|
+
async def turn_off(self):
|
597
|
+
#await self.vehicle.set_seat_heating('stop')
|
598
|
+
#await self.vehicle.update()
|
599
|
+
pass
|
600
|
+
|
601
|
+
@property
|
602
|
+
def assumed_state(self):
|
603
|
+
return False
|
604
|
+
|
605
|
+
#@property
|
606
|
+
#def attributes(self):
|
607
|
+
# return dict(last_result = self.vehicle.climater_action_status)
|
608
|
+
|
609
|
+
|
610
|
+
class BatteryClimatisation(Switch):
|
611
|
+
def __init__(self):
|
612
|
+
super().__init__(attr="climatisation_without_external_power", name="Climatisation from battery", icon="mdi:power-plug")
|
613
|
+
|
614
|
+
@property
|
615
|
+
def state(self):
|
616
|
+
return self.vehicle.climatisation_without_external_power
|
617
|
+
|
618
|
+
async def turn_on(self):
|
619
|
+
await self.vehicle.set_battery_climatisation(True)
|
620
|
+
await self.vehicle.update()
|
621
|
+
|
622
|
+
async def turn_off(self):
|
623
|
+
await self.vehicle.set_battery_climatisation(False)
|
624
|
+
await self.vehicle.update()
|
625
|
+
|
626
|
+
@property
|
627
|
+
def assumed_state(self):
|
628
|
+
return False
|
629
|
+
|
630
|
+
@property
|
631
|
+
def attributes(self):
|
632
|
+
return dict(last_result = self.vehicle.climater_action_status)
|
633
|
+
|
634
|
+
|
635
|
+
class PHeaterHeating(Switch):
|
636
|
+
def __init__(self):
|
637
|
+
super().__init__(attr="pheater_heating", name="Parking Heater Heating", icon="mdi:radiator")
|
638
|
+
|
639
|
+
def configurate(self, **config):
|
640
|
+
self.spin = config.get('spin', '')
|
641
|
+
self.duration = config.get('combustionengineheatingduration', 30)
|
642
|
+
|
643
|
+
@property
|
644
|
+
def state(self):
|
645
|
+
return self.vehicle.pheater_heating
|
646
|
+
|
647
|
+
async def turn_on(self):
|
648
|
+
await self.vehicle.set_pheater(mode='heating', spin=self.spin)
|
649
|
+
await self.vehicle.update()
|
650
|
+
|
651
|
+
async def turn_off(self):
|
652
|
+
await self.vehicle.set_pheater(mode='off', spin=self.spin)
|
653
|
+
await self.vehicle.update()
|
654
|
+
|
655
|
+
@property
|
656
|
+
def assumed_state(self):
|
657
|
+
return False
|
658
|
+
|
659
|
+
@property
|
660
|
+
def attributes(self):
|
661
|
+
return dict(last_result = self.vehicle.pheater_action_status)
|
662
|
+
|
663
|
+
|
664
|
+
class PHeaterVentilation(Switch):
|
665
|
+
def __init__(self):
|
666
|
+
super().__init__(attr="pheater_ventilation", name="Parking Heater Ventilation", icon="mdi:radiator")
|
667
|
+
|
668
|
+
def configurate(self, **config):
|
669
|
+
self.spin = config.get('spin', '')
|
670
|
+
self.duration = config.get('combustionengineclimatisationduration', 30)
|
671
|
+
|
672
|
+
@property
|
673
|
+
def state(self):
|
674
|
+
return self.vehicle.pheater_ventilation
|
675
|
+
|
676
|
+
async def turn_on(self):
|
677
|
+
await self.vehicle.set_pheater(mode='ventilation', spin=self.spin)
|
678
|
+
await self.vehicle.update()
|
679
|
+
|
680
|
+
async def turn_off(self):
|
681
|
+
await self.vehicle.set_pheater(mode='off', spin=self.spin)
|
682
|
+
await self.vehicle.update()
|
683
|
+
|
684
|
+
@property
|
685
|
+
def assumed_state(self):
|
686
|
+
return False
|
687
|
+
|
688
|
+
@property
|
689
|
+
def attributes(self):
|
690
|
+
return dict(last_result = self.vehicle.pheater_action_status)
|
691
|
+
|
692
|
+
|
693
|
+
class Warnings(Sensor):
|
694
|
+
def __init__(self):
|
695
|
+
super().__init__(attr="warnings", name="Warnings", icon="mdi:alarm-light")
|
696
|
+
|
697
|
+
@property
|
698
|
+
def state(self):
|
699
|
+
return self.vehicle.warnings
|
700
|
+
|
701
|
+
@property
|
702
|
+
def assumed_state(self):
|
703
|
+
return False
|
704
|
+
|
705
|
+
class DepartureTimer1(Switch):
|
706
|
+
def __init__(self):
|
707
|
+
super().__init__(attr="departure1", name="Departure timer 1", icon="mdi:radiator")
|
708
|
+
|
709
|
+
def configurate(self, **config):
|
710
|
+
self.spin = config.get('spin', '')
|
711
|
+
|
712
|
+
@property
|
713
|
+
def state(self):
|
714
|
+
status = self.vehicle.departure1.get("timerProgrammedStatus", "")
|
715
|
+
if status == "programmed":
|
716
|
+
return True
|
717
|
+
else:
|
718
|
+
return False
|
719
|
+
|
720
|
+
async def turn_on(self):
|
721
|
+
await self.vehicle.set_timer_active(id=1, action="on")
|
722
|
+
await self.vehicle.update()
|
723
|
+
|
724
|
+
async def turn_off(self):
|
725
|
+
await self.vehicle.set_timer_active(id=1, action="off")
|
726
|
+
await self.vehicle.update()
|
727
|
+
|
728
|
+
@property
|
729
|
+
def assumed_state(self):
|
730
|
+
return False
|
731
|
+
|
732
|
+
@property
|
733
|
+
def attributes(self):
|
734
|
+
return dict(self.vehicle.departure1)
|
735
|
+
|
736
|
+
|
737
|
+
class DepartureTimer2(Switch):
|
738
|
+
def __init__(self):
|
739
|
+
super().__init__(attr="departure2", name="Departure timer 2", icon="mdi:radiator")
|
740
|
+
|
741
|
+
def configurate(self, **config):
|
742
|
+
self.spin = config.get('spin', '')
|
743
|
+
|
744
|
+
@property
|
745
|
+
def state(self):
|
746
|
+
status = self.vehicle.departure2.get("timerProgrammedStatus", "")
|
747
|
+
if status == "programmed":
|
748
|
+
return True
|
749
|
+
else:
|
750
|
+
return False
|
751
|
+
|
752
|
+
async def turn_on(self):
|
753
|
+
await self.vehicle.set_timer_active(id=2, action="on")
|
754
|
+
await self.vehicle.update()
|
755
|
+
|
756
|
+
async def turn_off(self):
|
757
|
+
await self.vehicle.set_timer_active(id=2, action="off")
|
758
|
+
await self.vehicle.update()
|
759
|
+
|
760
|
+
@property
|
761
|
+
def assumed_state(self):
|
762
|
+
return False
|
763
|
+
|
764
|
+
@property
|
765
|
+
def attributes(self):
|
766
|
+
return dict(self.vehicle.departure2)
|
767
|
+
|
768
|
+
class DepartureTimer3(Switch):
|
769
|
+
def __init__(self):
|
770
|
+
super().__init__(attr="departure3", name="Departure timer 3", icon="mdi:radiator")
|
771
|
+
|
772
|
+
def configurate(self, **config):
|
773
|
+
self.spin = config.get('spin', '')
|
774
|
+
|
775
|
+
@property
|
776
|
+
def state(self):
|
777
|
+
status = self.vehicle.departure3.get("timerProgrammedStatus", "")
|
778
|
+
if status == "programmed":
|
779
|
+
return True
|
780
|
+
else:
|
781
|
+
return False
|
782
|
+
|
783
|
+
async def turn_on(self):
|
784
|
+
await self.vehicle.set_timer_active(id=3, action="on")
|
785
|
+
await self.vehicle.update()
|
786
|
+
|
787
|
+
async def turn_off(self):
|
788
|
+
await self.vehicle.set_timer_active(id=3, action="off")
|
789
|
+
await self.vehicle.update()
|
790
|
+
|
791
|
+
@property
|
792
|
+
def assumed_state(self):
|
793
|
+
return False
|
794
|
+
|
795
|
+
@property
|
796
|
+
def attributes(self):
|
797
|
+
return dict(self.vehicle.departure3)
|
798
|
+
|
799
|
+
|
800
|
+
class RequestResults(Sensor):
|
801
|
+
def __init__(self):
|
802
|
+
super().__init__(attr="request_results", name="Request results", icon="mdi:chat-alert", unit=None)
|
803
|
+
|
804
|
+
@property
|
805
|
+
def state(self):
|
806
|
+
if self.vehicle.request_results.get('state', False):
|
807
|
+
return self.vehicle.request_results.get('state')
|
808
|
+
return 'N/A'
|
809
|
+
|
810
|
+
@property
|
811
|
+
def assumed_state(self):
|
812
|
+
return False
|
813
|
+
|
814
|
+
@property
|
815
|
+
def attributes(self):
|
816
|
+
return dict(self.vehicle.request_results)
|
817
|
+
|
818
|
+
|
819
|
+
def create_instruments():
|
820
|
+
return [
|
821
|
+
Position(),
|
822
|
+
DoorLock(),
|
823
|
+
TrunkLock(),
|
824
|
+
RequestFlash(),
|
825
|
+
RequestHonkAndFlash(),
|
826
|
+
RequestUpdate(),
|
827
|
+
WindowHeater(),
|
828
|
+
BatteryClimatisation(),
|
829
|
+
ElectricClimatisation(),
|
830
|
+
AuxiliaryClimatisation(),
|
831
|
+
PHeaterVentilation(),
|
832
|
+
PHeaterHeating(),
|
833
|
+
#ElectricClimatisationClimate(),
|
834
|
+
#CombustionClimatisationClimate(),
|
835
|
+
Charging(),
|
836
|
+
Warnings(),
|
837
|
+
RequestResults(),
|
838
|
+
DepartureTimer1(),
|
839
|
+
DepartureTimer2(),
|
840
|
+
DepartureTimer3(),
|
841
|
+
Sensor(
|
842
|
+
attr="distance",
|
843
|
+
name="Odometer",
|
844
|
+
icon="mdi:speedometer",
|
845
|
+
unit="km",
|
846
|
+
device_class="distance"
|
847
|
+
),
|
848
|
+
Sensor(
|
849
|
+
attr="battery_level",
|
850
|
+
name="Battery level",
|
851
|
+
icon="mdi:battery",
|
852
|
+
unit="%",
|
853
|
+
device_class="battery"
|
854
|
+
),
|
855
|
+
Sensor(
|
856
|
+
attr="min_charge_level",
|
857
|
+
name="Minimum charge level",
|
858
|
+
icon="mdi:battery-positive",
|
859
|
+
unit="%",
|
860
|
+
device_class="battery"
|
861
|
+
),
|
862
|
+
Sensor(
|
863
|
+
attr="adblue_level",
|
864
|
+
name="Adblue level",
|
865
|
+
icon="mdi:fuel",
|
866
|
+
unit="km",
|
867
|
+
device_class="distance"
|
868
|
+
),
|
869
|
+
Sensor(
|
870
|
+
attr="fuel_level",
|
871
|
+
name="Fuel level",
|
872
|
+
icon="mdi:fuel",
|
873
|
+
unit="%",
|
874
|
+
),
|
875
|
+
Sensor(
|
876
|
+
attr="service_inspection",
|
877
|
+
name="Service inspection days",
|
878
|
+
icon="mdi:garage",
|
879
|
+
unit="days",
|
880
|
+
),
|
881
|
+
Sensor(
|
882
|
+
attr="service_inspection_distance",
|
883
|
+
name="Service inspection distance",
|
884
|
+
icon="mdi:garage",
|
885
|
+
unit="km",
|
886
|
+
device_class="distance"
|
887
|
+
),
|
888
|
+
Sensor(
|
889
|
+
attr="oil_inspection",
|
890
|
+
name="Oil inspection days",
|
891
|
+
icon="mdi:oil",
|
892
|
+
unit="days",
|
893
|
+
),
|
894
|
+
Sensor(
|
895
|
+
attr="oil_inspection_distance",
|
896
|
+
name="Oil inspection distance",
|
897
|
+
icon="mdi:oil",
|
898
|
+
unit="km",
|
899
|
+
device_class="distance"
|
900
|
+
),
|
901
|
+
Sensor(
|
902
|
+
attr="last_connected",
|
903
|
+
name="Last connected",
|
904
|
+
icon="mdi:clock",
|
905
|
+
device_class="timestamp"
|
906
|
+
),
|
907
|
+
Sensor(
|
908
|
+
attr="parking_time",
|
909
|
+
name="Parking time",
|
910
|
+
icon="mdi:clock",
|
911
|
+
device_class="timestamp"
|
912
|
+
),
|
913
|
+
Sensor(
|
914
|
+
attr="charging_time_left",
|
915
|
+
name="Charging time left",
|
916
|
+
icon="mdi:battery-charging-100",
|
917
|
+
unit="min",
|
918
|
+
device_class="duration"
|
919
|
+
),
|
920
|
+
Sensor(
|
921
|
+
attr="charging_power",
|
922
|
+
name="Charging power",
|
923
|
+
icon="mdi:flash",
|
924
|
+
unit="W",
|
925
|
+
device_class="power"
|
926
|
+
),
|
927
|
+
Sensor(
|
928
|
+
attr="charge_rate",
|
929
|
+
name="Charging rate",
|
930
|
+
icon="mdi:battery-heart",
|
931
|
+
unit="km/h",
|
932
|
+
device_class="speed"
|
933
|
+
),
|
934
|
+
Sensor(
|
935
|
+
attr="electric_range",
|
936
|
+
name="Electric range",
|
937
|
+
icon="mdi:car-electric",
|
938
|
+
unit="km",
|
939
|
+
device_class="distance"
|
940
|
+
),
|
941
|
+
Sensor(
|
942
|
+
attr="combustion_range",
|
943
|
+
name="Combustion range",
|
944
|
+
icon="mdi:car",
|
945
|
+
unit="km",
|
946
|
+
device_class="distance"
|
947
|
+
),
|
948
|
+
Sensor(
|
949
|
+
attr="combined_range",
|
950
|
+
name="Combined range",
|
951
|
+
icon="mdi:car",
|
952
|
+
unit="km",
|
953
|
+
device_class="distance"
|
954
|
+
),
|
955
|
+
Sensor(
|
956
|
+
attr="charge_max_ampere",
|
957
|
+
name="Charger max ampere",
|
958
|
+
icon="mdi:flash",
|
959
|
+
unit="A",
|
960
|
+
device_class="current"
|
961
|
+
),
|
962
|
+
Sensor(
|
963
|
+
attr="climatisation_target_temperature",
|
964
|
+
name="Climatisation target temperature",
|
965
|
+
icon="mdi:thermometer",
|
966
|
+
unit="°C",
|
967
|
+
device_class="temperature"
|
968
|
+
),
|
969
|
+
Sensor(
|
970
|
+
attr="climatisation_time_left",
|
971
|
+
name="Climatisation time left",
|
972
|
+
icon="mdi:clock",
|
973
|
+
unit="h",
|
974
|
+
device_class="duration"
|
975
|
+
),
|
976
|
+
Sensor(
|
977
|
+
attr="trip_last_average_speed",
|
978
|
+
name="Last trip average speed",
|
979
|
+
icon="mdi:speedometer",
|
980
|
+
unit="km/h",
|
981
|
+
device_class="speed"
|
982
|
+
),
|
983
|
+
Sensor(
|
984
|
+
attr="trip_last_average_electric_consumption",
|
985
|
+
name="Last trip average electric consumption",
|
986
|
+
icon="mdi:car-battery",
|
987
|
+
unit="kWh/100km",
|
988
|
+
device_class="energy_distance"
|
989
|
+
),
|
990
|
+
Sensor(
|
991
|
+
attr="trip_last_average_fuel_consumption",
|
992
|
+
name="Last trip average fuel consumption",
|
993
|
+
icon="mdi:fuel",
|
994
|
+
unit="l/100km",
|
995
|
+
),
|
996
|
+
Sensor(
|
997
|
+
attr="trip_last_duration",
|
998
|
+
name="Last trip duration",
|
999
|
+
icon="mdi:clock",
|
1000
|
+
unit="min",
|
1001
|
+
device_class="duration"
|
1002
|
+
),
|
1003
|
+
Sensor(
|
1004
|
+
attr="trip_last_length",
|
1005
|
+
name="Last trip length",
|
1006
|
+
icon="mdi:map-marker-distance",
|
1007
|
+
unit="km",
|
1008
|
+
device_class="distance"
|
1009
|
+
),
|
1010
|
+
Sensor(
|
1011
|
+
attr="trip_last_recuperation",
|
1012
|
+
name="Last trip recuperation",
|
1013
|
+
icon="mdi:battery-plus",
|
1014
|
+
unit="kWh/100km",
|
1015
|
+
device_class="energy_distance"
|
1016
|
+
),
|
1017
|
+
Sensor(
|
1018
|
+
attr="trip_last_average_recuperation",
|
1019
|
+
name="Last trip average recuperation",
|
1020
|
+
icon="mdi:battery-plus",
|
1021
|
+
unit="kWh/100km",
|
1022
|
+
device_class="energy_distance"
|
1023
|
+
),
|
1024
|
+
Sensor(
|
1025
|
+
attr="trip_last_average_auxillary_consumption",
|
1026
|
+
name="Last trip average auxillary consumption",
|
1027
|
+
icon="mdi:flash",
|
1028
|
+
unit="kWh/100km",
|
1029
|
+
device_class="energy_distance"
|
1030
|
+
),
|
1031
|
+
Sensor(
|
1032
|
+
attr="trip_last_average_aux_consumer_consumption",
|
1033
|
+
name="Last trip average auxillary consumer consumption",
|
1034
|
+
icon="mdi:flash",
|
1035
|
+
unit="kWh/100km",
|
1036
|
+
device_class="energy_distance"
|
1037
|
+
),
|
1038
|
+
Sensor(
|
1039
|
+
attr="trip_last_total_electric_consumption",
|
1040
|
+
name="Last trip total electric consumption",
|
1041
|
+
icon="mdi:car-battery",
|
1042
|
+
unit="kWh/100km",
|
1043
|
+
device_class="energy_distance"
|
1044
|
+
),
|
1045
|
+
Sensor(
|
1046
|
+
attr="trip_last_cycle_average_speed",
|
1047
|
+
name="Last cycle average speed",
|
1048
|
+
icon="mdi:speedometer",
|
1049
|
+
unit="km/h",
|
1050
|
+
device_class="speed"
|
1051
|
+
),
|
1052
|
+
Sensor(
|
1053
|
+
attr="trip_last_cycle_average_electric_consumption",
|
1054
|
+
name="Last cycle_average electric consumption",
|
1055
|
+
icon="mdi:car-battery",
|
1056
|
+
unit="kWh/100km",
|
1057
|
+
device_class="energy_distance"
|
1058
|
+
),
|
1059
|
+
Sensor(
|
1060
|
+
attr="trip_last_cycle_average_fuel_consumption",
|
1061
|
+
name="Last cycle average fuel consumption",
|
1062
|
+
icon="mdi:fuel",
|
1063
|
+
unit="l/100km",
|
1064
|
+
),
|
1065
|
+
Sensor(
|
1066
|
+
attr="trip_last_cycle_average_auxillary_consumption",
|
1067
|
+
name="Last cycle average auxillary consumption",
|
1068
|
+
icon="mdi:flash",
|
1069
|
+
unit="kWh/100km",
|
1070
|
+
device_class="energy_distance"
|
1071
|
+
),
|
1072
|
+
Sensor(
|
1073
|
+
attr="trip_last_cycle_duration",
|
1074
|
+
name="Last cycle duration",
|
1075
|
+
icon="mdi:clock",
|
1076
|
+
unit="min",
|
1077
|
+
device_class="duration"
|
1078
|
+
),
|
1079
|
+
Sensor(
|
1080
|
+
attr="trip_last_cycle_length",
|
1081
|
+
name="Last cycle length",
|
1082
|
+
icon="mdi:map-marker-distance",
|
1083
|
+
unit="km",
|
1084
|
+
device_class="distance"
|
1085
|
+
),
|
1086
|
+
Sensor(
|
1087
|
+
attr="trip_last_cycle_recuperation",
|
1088
|
+
name="Last cycle recuperation",
|
1089
|
+
icon="mdi:battery-plus",
|
1090
|
+
unit="kWh/100km",
|
1091
|
+
device_class="energy_distance"
|
1092
|
+
),
|
1093
|
+
Sensor(
|
1094
|
+
attr="trip_last_cycle_average_recuperation",
|
1095
|
+
name="Last cycle average recuperation",
|
1096
|
+
icon="mdi:battery-plus",
|
1097
|
+
unit="kWh/100km",
|
1098
|
+
device_class="energy_distance"
|
1099
|
+
),
|
1100
|
+
Sensor(
|
1101
|
+
attr="trip_last_cycle_average_aux_consumer_consumption",
|
1102
|
+
name="Last cycle average auxillary consumer consumption",
|
1103
|
+
icon="mdi:flash",
|
1104
|
+
unit="kWh/100km",
|
1105
|
+
device_class="energy_distance"
|
1106
|
+
),
|
1107
|
+
Sensor(
|
1108
|
+
attr="trip_last_cycle_total_electric_consumption",
|
1109
|
+
name="Last cycle total electric consumption",
|
1110
|
+
icon="mdi:car-battery",
|
1111
|
+
unit="kWh/100km",
|
1112
|
+
device_class="energy_distance"
|
1113
|
+
),
|
1114
|
+
Sensor(
|
1115
|
+
attr="model_image_large",
|
1116
|
+
name="Model image URL (Large)",
|
1117
|
+
icon="mdi:file-image",
|
1118
|
+
),
|
1119
|
+
Sensor(
|
1120
|
+
attr="model_image_small",
|
1121
|
+
name="Model image URL (Small)",
|
1122
|
+
icon="mdi:file-image",
|
1123
|
+
),
|
1124
|
+
Sensor(
|
1125
|
+
attr="pheater_status",
|
1126
|
+
name="Parking Heater heating/ventilation status",
|
1127
|
+
icon="mdi:radiator",
|
1128
|
+
),
|
1129
|
+
Sensor(
|
1130
|
+
attr="pheater_duration",
|
1131
|
+
name="Parking Heater heating/ventilation duration",
|
1132
|
+
icon="mdi:timer",
|
1133
|
+
unit="minutes",
|
1134
|
+
device_class="duration"
|
1135
|
+
),
|
1136
|
+
Sensor(
|
1137
|
+
attr="outside_temperature",
|
1138
|
+
name="Outside temperature",
|
1139
|
+
icon="mdi:thermometer",
|
1140
|
+
unit="°C",
|
1141
|
+
device_class="temperature"
|
1142
|
+
),
|
1143
|
+
Sensor(
|
1144
|
+
attr="requests_remaining",
|
1145
|
+
name="Requests remaining",
|
1146
|
+
icon="mdi:chat-alert",
|
1147
|
+
unit="",
|
1148
|
+
),
|
1149
|
+
BinarySensor(
|
1150
|
+
attr="external_power",
|
1151
|
+
name="External power",
|
1152
|
+
device_class="power"
|
1153
|
+
),
|
1154
|
+
BinarySensor(
|
1155
|
+
attr="energy_flow",
|
1156
|
+
name="Energy flow",
|
1157
|
+
device_class="power"
|
1158
|
+
),
|
1159
|
+
BinarySensor(
|
1160
|
+
attr="parking_light",
|
1161
|
+
name="Parking light",
|
1162
|
+
device_class="light",
|
1163
|
+
icon="mdi:car-parking-lights"
|
1164
|
+
),
|
1165
|
+
BinarySensor(
|
1166
|
+
attr="door_locked",
|
1167
|
+
name="Doors locked",
|
1168
|
+
device_class="lock",
|
1169
|
+
reverse_state=False
|
1170
|
+
),
|
1171
|
+
BinarySensor(
|
1172
|
+
attr="door_closed_left_front",
|
1173
|
+
name="Door closed left front",
|
1174
|
+
device_class="door",
|
1175
|
+
reverse_state=False,
|
1176
|
+
icon="mdi:car-door"
|
1177
|
+
),
|
1178
|
+
BinarySensor(
|
1179
|
+
attr="door_closed_right_front",
|
1180
|
+
name="Door closed right front",
|
1181
|
+
device_class="door",
|
1182
|
+
reverse_state=False,
|
1183
|
+
icon="mdi:car-door"
|
1184
|
+
),
|
1185
|
+
BinarySensor(
|
1186
|
+
attr="door_closed_left_back",
|
1187
|
+
name="Door closed left back",
|
1188
|
+
device_class="door",
|
1189
|
+
reverse_state=False,
|
1190
|
+
icon="mdi:car-door"
|
1191
|
+
),
|
1192
|
+
BinarySensor(
|
1193
|
+
attr="door_closed_right_back",
|
1194
|
+
name="Door closed right back",
|
1195
|
+
device_class="door",
|
1196
|
+
reverse_state=False,
|
1197
|
+
icon="mdi:car-door"
|
1198
|
+
),
|
1199
|
+
BinarySensor(
|
1200
|
+
attr="trunk_locked",
|
1201
|
+
name="Trunk locked",
|
1202
|
+
device_class="lock",
|
1203
|
+
reverse_state=False
|
1204
|
+
),
|
1205
|
+
BinarySensor(
|
1206
|
+
attr="trunk_closed",
|
1207
|
+
name="Trunk closed",
|
1208
|
+
device_class="door",
|
1209
|
+
reverse_state=False
|
1210
|
+
),
|
1211
|
+
BinarySensor(
|
1212
|
+
attr="hood_closed",
|
1213
|
+
name="Hood closed",
|
1214
|
+
device_class="door",
|
1215
|
+
reverse_state=False
|
1216
|
+
),
|
1217
|
+
BinarySensor(
|
1218
|
+
attr="charging_cable_connected",
|
1219
|
+
name="Charging cable connected",
|
1220
|
+
device_class="plug",
|
1221
|
+
reverse_state=False
|
1222
|
+
),
|
1223
|
+
BinarySensor(
|
1224
|
+
attr="charging_cable_locked",
|
1225
|
+
name="Charging cable locked",
|
1226
|
+
device_class="lock",
|
1227
|
+
reverse_state=False
|
1228
|
+
),
|
1229
|
+
BinarySensor(
|
1230
|
+
attr="sunroof_closed",
|
1231
|
+
name="Sunroof closed",
|
1232
|
+
device_class="window",
|
1233
|
+
reverse_state=False
|
1234
|
+
),
|
1235
|
+
BinarySensor(
|
1236
|
+
attr="windows_closed",
|
1237
|
+
name="Windows closed",
|
1238
|
+
device_class="window",
|
1239
|
+
reverse_state=False
|
1240
|
+
),
|
1241
|
+
BinarySensor(
|
1242
|
+
attr="window_closed_left_front",
|
1243
|
+
name="Window closed left front",
|
1244
|
+
device_class="window",
|
1245
|
+
reverse_state=False
|
1246
|
+
),
|
1247
|
+
BinarySensor(
|
1248
|
+
attr="window_closed_left_back",
|
1249
|
+
name="Window closed left back",
|
1250
|
+
device_class="window",
|
1251
|
+
reverse_state=False
|
1252
|
+
),
|
1253
|
+
BinarySensor(
|
1254
|
+
attr="window_closed_right_front",
|
1255
|
+
name="Window closed right front",
|
1256
|
+
device_class="window",
|
1257
|
+
reverse_state=False
|
1258
|
+
),
|
1259
|
+
BinarySensor(
|
1260
|
+
attr="window_closed_right_back",
|
1261
|
+
name="Window closed right back",
|
1262
|
+
device_class="window",
|
1263
|
+
reverse_state=False
|
1264
|
+
),
|
1265
|
+
BinarySensor(
|
1266
|
+
attr="vehicle_moving",
|
1267
|
+
name="Vehicle Moving",
|
1268
|
+
device_class="moving"
|
1269
|
+
),
|
1270
|
+
BinarySensor(
|
1271
|
+
attr="request_in_progress",
|
1272
|
+
name="Request in progress",
|
1273
|
+
device_class="connectivity"
|
1274
|
+
),
|
1275
|
+
]
|
1276
|
+
|
1277
|
+
|
1278
|
+
class Dashboard:
|
1279
|
+
def __init__(self, vehicle, **config):
|
1280
|
+
self._config = config
|
1281
|
+
self.instruments = [
|
1282
|
+
instrument
|
1283
|
+
for instrument in create_instruments()
|
1284
|
+
if instrument.setup(vehicle, **config)
|
1285
|
+
]
|
1286
|
+
_LOGGER.debug("Supported instruments: " + ", ".join(str(inst.attr) for inst in self.instruments))
|
1287
|
+
|