carconnectivity-plugin-database 0.1a19__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.
Files changed (48) hide show
  1. carconnectivity_database/__init__.py +0 -0
  2. carconnectivity_database/carconnectivity_database_base.py +26 -0
  3. carconnectivity_plugin_database-0.1a19.dist-info/METADATA +74 -0
  4. carconnectivity_plugin_database-0.1a19.dist-info/RECORD +48 -0
  5. carconnectivity_plugin_database-0.1a19.dist-info/WHEEL +5 -0
  6. carconnectivity_plugin_database-0.1a19.dist-info/entry_points.txt +2 -0
  7. carconnectivity_plugin_database-0.1a19.dist-info/licenses/LICENSE +21 -0
  8. carconnectivity_plugin_database-0.1a19.dist-info/top_level.txt +2 -0
  9. carconnectivity_plugins/database/__init__.py +0 -0
  10. carconnectivity_plugins/database/_version.py +34 -0
  11. carconnectivity_plugins/database/agents/base_agent.py +2 -0
  12. carconnectivity_plugins/database/agents/charging_agent.py +523 -0
  13. carconnectivity_plugins/database/agents/climatization_agent.py +80 -0
  14. carconnectivity_plugins/database/agents/drive_state_agent.py +368 -0
  15. carconnectivity_plugins/database/agents/state_agent.py +162 -0
  16. carconnectivity_plugins/database/agents/trip_agent.py +225 -0
  17. carconnectivity_plugins/database/model/__init__.py +19 -0
  18. carconnectivity_plugins/database/model/alembic.ini +100 -0
  19. carconnectivity_plugins/database/model/base.py +4 -0
  20. carconnectivity_plugins/database/model/carconnectivity_schema/README +1 -0
  21. carconnectivity_plugins/database/model/carconnectivity_schema/__init__.py +0 -0
  22. carconnectivity_plugins/database/model/carconnectivity_schema/env.py +78 -0
  23. carconnectivity_plugins/database/model/carconnectivity_schema/script.py.mako +24 -0
  24. carconnectivity_plugins/database/model/carconnectivity_schema/versions/__init__.py +0 -0
  25. carconnectivity_plugins/database/model/charging_power.py +52 -0
  26. carconnectivity_plugins/database/model/charging_rate.py +52 -0
  27. carconnectivity_plugins/database/model/charging_session.py +144 -0
  28. carconnectivity_plugins/database/model/charging_state.py +57 -0
  29. carconnectivity_plugins/database/model/charging_station.py +66 -0
  30. carconnectivity_plugins/database/model/climatization_state.py +57 -0
  31. carconnectivity_plugins/database/model/connection_state.py +57 -0
  32. carconnectivity_plugins/database/model/datetime_decorator.py +44 -0
  33. carconnectivity_plugins/database/model/drive.py +89 -0
  34. carconnectivity_plugins/database/model/drive_consumption.py +48 -0
  35. carconnectivity_plugins/database/model/drive_level.py +50 -0
  36. carconnectivity_plugins/database/model/drive_range.py +53 -0
  37. carconnectivity_plugins/database/model/drive_range_full.py +53 -0
  38. carconnectivity_plugins/database/model/location.py +85 -0
  39. carconnectivity_plugins/database/model/migrations.py +24 -0
  40. carconnectivity_plugins/database/model/outside_temperature.py +52 -0
  41. carconnectivity_plugins/database/model/state.py +56 -0
  42. carconnectivity_plugins/database/model/tag.py +31 -0
  43. carconnectivity_plugins/database/model/timedelta_decorator.py +33 -0
  44. carconnectivity_plugins/database/model/trip.py +85 -0
  45. carconnectivity_plugins/database/model/vehicle.py +186 -0
  46. carconnectivity_plugins/database/plugin.py +167 -0
  47. carconnectivity_plugins/database/ui/plugin_ui.py +48 -0
  48. carconnectivity_plugins/database/ui/templates/database/status.html +8 -0
@@ -0,0 +1,368 @@
1
+ from __future__ import annotations
2
+ from typing import TYPE_CHECKING
3
+
4
+ import threading
5
+
6
+ import logging
7
+
8
+ from sqlalchemy.exc import DatabaseError
9
+
10
+ from carconnectivity.observable import Observable
11
+ from carconnectivity.drive import GenericDrive, ElectricDrive, CombustionDrive
12
+
13
+ from carconnectivity_plugins.database.agents.base_agent import BaseAgent
14
+ from carconnectivity_plugins.database.model.drive_level import DriveLevel
15
+ from carconnectivity_plugins.database.model.drive_range import DriveRange
16
+ from carconnectivity_plugins.database.model.drive_consumption import DriveConsumption
17
+ from carconnectivity_plugins.database.model.drive_range_full import DriveRangeEstimatedFull
18
+
19
+ if TYPE_CHECKING:
20
+ from typing import Optional
21
+ from sqlalchemy.orm import scoped_session
22
+ from sqlalchemy.orm.session import Session
23
+
24
+ from carconnectivity.attributes import LevelAttribute, RangeAttribute, EnumAttribute, EnergyAttribute, VolumeAttribute, EnergyConsumptionAttribute, \
25
+ FuelConsumptionAttribute
26
+
27
+ from carconnectivity_plugins.database.plugin import Plugin
28
+ from carconnectivity_plugins.database.model.drive import Drive
29
+
30
+
31
+ LOG: logging.Logger = logging.getLogger("carconnectivity.plugins.database.agents.drive_state_agent")
32
+
33
+
34
+ class DriveStateAgent(BaseAgent):
35
+ def __init__(self, database_plugin: Plugin, session_factory: scoped_session[Session], drive: Drive) -> None:
36
+ self.database_plugin: Plugin = database_plugin
37
+ self.session_factory: scoped_session[Session] = session_factory
38
+ self.drive: Drive = drive
39
+ if self.drive is None or self.drive.carconnectivity_drive is None:
40
+ raise ValueError("Drive or its carconnectivity_drive attribute is None")
41
+
42
+ self.drive.carconnectivity_drive.type.add_observer(self.__on_type_change, Observable.ObserverEvent.VALUE_CHANGED, on_transaction_end=True)
43
+ self.type_lock: threading.RLock = threading.RLock()
44
+ self.__on_type_change(self.drive.carconnectivity_drive.type, Observable.ObserverEvent.VALUE_CHANGED)
45
+
46
+ self.drive.carconnectivity_drive.range_wltp.add_observer(self.__on_range_wltp_change, Observable.ObserverEvent.VALUE_CHANGED, on_transaction_end=True)
47
+ self.range_wltp_lock: threading.RLock = threading.RLock()
48
+ self.__on_range_wltp_change(self.drive.carconnectivity_drive.range_wltp, Observable.ObserverEvent.VALUE_CHANGED)
49
+
50
+ with self.session_factory() as session:
51
+ self.drive = session.merge(self.drive)
52
+ session.refresh(self.drive)
53
+
54
+ if isinstance(self.drive.carconnectivity_drive, ElectricDrive):
55
+ self.drive.carconnectivity_drive.battery.total_capacity.add_observer(self.__on_electric_total_capacity_change,
56
+ Observable.ObserverEvent.VALUE_CHANGED, on_transaction_end=True)
57
+ self.total_capacity_lock: threading.RLock = threading.RLock()
58
+ self.__on_electric_total_capacity_change(self.drive.carconnectivity_drive.battery.total_capacity, Observable.ObserverEvent.VALUE_CHANGED)
59
+
60
+ self.drive.carconnectivity_drive.battery.available_capacity.add_observer(self.__on_electric_available_capacity_change,
61
+ Observable.ObserverEvent.VALUE_CHANGED, on_transaction_end=True)
62
+ self.available_capacity_lock: threading.RLock = threading.RLock()
63
+ self.__on_electric_available_capacity_change(self.drive.carconnectivity_drive.battery.available_capacity,
64
+ Observable.ObserverEvent.VALUE_CHANGED)
65
+
66
+ self.last_electric_consumption: Optional[DriveConsumption] = session.query(DriveConsumption) \
67
+ .filter(DriveConsumption.drive_id == self.drive.id).order_by(DriveConsumption.first_date.desc()).first()
68
+ self.last_electric_consumption_lock: threading.RLock = threading.RLock()
69
+ self.drive.carconnectivity_drive.consumption.add_observer(self.__on_electric_consumption_change,
70
+ Observable.ObserverEvent.VALUE_CHANGED)
71
+ self.__on_electric_consumption_change(self.drive.carconnectivity_drive.consumption, Observable.ObserverEvent.UPDATED)
72
+
73
+ elif isinstance(self.drive.carconnectivity_drive, CombustionDrive):
74
+ self.drive.carconnectivity_drive.fuel_tank.available_capacity.add_observer(self.__on_fuel_available_capacity_change,
75
+ Observable.ObserverEvent.VALUE_CHANGED, on_transaction_end=True)
76
+ self.fuel_available_capacity_lock: threading.RLock = threading.RLock()
77
+ self.__on_fuel_available_capacity_change(self.drive.carconnectivity_drive.fuel_tank.available_capacity, Observable.ObserverEvent.VALUE_CHANGED)
78
+
79
+ self.last_fuel_consumption: Optional[DriveConsumption] = session.query(DriveConsumption).filter(DriveConsumption.drive_id == self.drive.id) \
80
+ .order_by(DriveConsumption.first_date.desc()).first()
81
+ self.last_fuel_consumption_lock: threading.RLock = threading.RLock()
82
+ self.drive.carconnectivity_drive.consumption.add_observer(self.__on_fuel_consumption_change,
83
+ Observable.ObserverEvent.VALUE_CHANGED)
84
+ self.__on_fuel_consumption_change(self.drive.carconnectivity_drive.consumption, Observable.ObserverEvent.UPDATED)
85
+
86
+ self.last_level: Optional[DriveLevel] = session.query(DriveLevel).filter(DriveLevel.drive_id == self.drive.id) \
87
+ .order_by(DriveLevel.first_date.desc()).first()
88
+ self.last_level_lock: threading.RLock = threading.RLock()
89
+ self.last_range: Optional[DriveRange] = session.query(DriveRange).filter(DriveRange.drive_id == self.drive.id) \
90
+ .order_by(DriveRange.first_date.desc()).first()
91
+ self.last_range_lock: threading.RLock = threading.RLock()
92
+ self.last_range_estimated_full: Optional[DriveRangeEstimatedFull] = session.query(DriveRangeEstimatedFull) \
93
+ .filter(DriveRangeEstimatedFull.drive_id == self.drive.id).order_by(DriveRangeEstimatedFull.first_date.desc()).first()
94
+ self.last_range_estimated_full_lock: threading.RLock = threading.RLock()
95
+
96
+ if self.drive.carconnectivity_drive is not None:
97
+ self.drive.carconnectivity_drive.level.add_observer(self.__on_level_change, Observable.ObserverEvent.UPDATED)
98
+ if self.drive.carconnectivity_drive.level.enabled:
99
+ self.__on_level_change(self.drive.carconnectivity_drive.level, Observable.ObserverEvent.UPDATED)
100
+
101
+ self.drive.carconnectivity_drive.range.add_observer(self.__on_range_change, Observable.ObserverEvent.UPDATED)
102
+ if self.drive.carconnectivity_drive.range.enabled:
103
+ self.__on_range_change(self.drive.carconnectivity_drive.range, Observable.ObserverEvent.UPDATED)
104
+
105
+ self.drive.carconnectivity_drive.range_estimated_full.add_observer(self.__on_range_estimated_full_change, Observable.ObserverEvent.UPDATED)
106
+ if self.drive.carconnectivity_drive.range_estimated_full.enabled:
107
+ self.__on_range_estimated_full_change(self.drive.carconnectivity_drive.range_estimated_full, Observable.ObserverEvent.UPDATED)
108
+ session_factory.remove()
109
+
110
+ def __on_level_change(self, element: LevelAttribute, flags: Observable.ObserverEvent) -> None:
111
+ del flags
112
+ if element.enabled:
113
+ with self.last_level_lock:
114
+ with self.session_factory() as session:
115
+ self.drive = session.merge(self.drive)
116
+ session.refresh(self.drive)
117
+ if self.last_level is not None:
118
+ self.last_level = session.merge(self.last_level)
119
+ session.refresh(self.last_level)
120
+ if (self.last_level is None or self.last_level.level != element.value) \
121
+ and element.last_updated is not None:
122
+ new_level: DriveLevel = DriveLevel(drive_id=self.drive.id, first_date=element.last_updated, last_date=element.last_updated,
123
+ level=element.value)
124
+ try:
125
+ session.add(new_level)
126
+ session.commit()
127
+ LOG.debug('Added new level %s for drive %s to database', element.value, self.drive.id)
128
+ self.last_level = new_level
129
+ except DatabaseError as err:
130
+ session.rollback()
131
+ LOG.error('DatabaseError while adding level for drive %s to database: %s', self.drive.id, err)
132
+ self.database_plugin.healthy._set_value(value=False) # pylint: disable=protected-access
133
+ elif self.last_level is not None and self.last_level.level == element.value \
134
+ and element.last_updated is not None:
135
+ if self.last_level.last_date is None or element.last_updated > self.last_level.last_date:
136
+ try:
137
+ self.last_level.last_date = element.last_updated
138
+ session.commit()
139
+ LOG.debug('Updated level %s for drive %s in database', element.value, self.drive.id)
140
+ except DatabaseError as err:
141
+ session.rollback()
142
+ LOG.error('DatabaseError while updating level for drive %s in database: %s', self.drive.id, err)
143
+ self.database_plugin.healthy._set_value(value=False) # pylint: disable=protected-access
144
+ self.session_factory.remove()
145
+
146
+ def __on_range_change(self, element: RangeAttribute, flags: Observable.ObserverEvent) -> None:
147
+ del flags
148
+ if element.enabled:
149
+ with self.last_range_lock:
150
+ with self.session_factory() as session:
151
+ self.drive = session.merge(self.drive)
152
+ session.refresh(self.drive)
153
+ if self.last_range is not None:
154
+ self.last_range = session.merge(self.last_range)
155
+ session.refresh(self.last_range)
156
+ if (self.last_range is None or self.last_range.range != element.value) \
157
+ and element.last_updated is not None:
158
+ new_range: DriveRange = DriveRange(drive_id=self.drive.id, first_date=element.last_updated, last_date=element.last_updated,
159
+ range=element.value)
160
+ try:
161
+ session.add(new_range)
162
+ session.commit()
163
+ LOG.debug('Added new range %s for drive %s to database', element.value, self.drive.id)
164
+ self.last_range = new_range
165
+ except DatabaseError as err:
166
+ session.rollback()
167
+ LOG.error('DatabaseError while adding range for drive %s to database: %s', self.drive.id, err)
168
+ self.database_plugin.healthy._set_value(value=False) # pylint: disable=protected-access
169
+ elif self.last_range is not None and self.last_range.range == element.value \
170
+ and element.last_updated is not None:
171
+ if self.last_range.last_date is None or element.last_updated > self.last_range.last_date:
172
+ try:
173
+ self.last_range.last_date = element.last_updated
174
+ session.commit()
175
+ LOG.debug('Updated range %s for drive %s in database', element.value, self.drive.id)
176
+ except DatabaseError as err:
177
+ session.rollback()
178
+ LOG.error('DatabaseError while updating range for drive %s in database: %s', self.drive.id, err)
179
+ self.database_plugin.healthy._set_value(value=False) # pylint: disable=protected-access
180
+ self.session_factory.remove()
181
+
182
+ def __on_range_estimated_full_change(self, element: RangeAttribute, flags: Observable.ObserverEvent) -> None:
183
+ del flags
184
+ if element.enabled:
185
+ with self.last_range_estimated_full_lock:
186
+ with self.session_factory() as session:
187
+ self.drive = session.merge(self.drive)
188
+ session.refresh(self.drive)
189
+ if self.last_range_estimated_full is not None:
190
+ self.last_range_estimated_full = session.merge(self.last_range_estimated_full)
191
+ session.refresh(self.last_range_estimated_full)
192
+ if (self.last_range_estimated_full is None or self.last_range_estimated_full.range_estimated_full != element.value) \
193
+ and element.last_updated is not None:
194
+ new_range: DriveRangeEstimatedFull = DriveRangeEstimatedFull(drive_id=self.drive.id, first_date=element.last_updated,
195
+ last_date=element.last_updated, range_estimated_full=element.value)
196
+ try:
197
+ session.add(new_range)
198
+ session.commit()
199
+ LOG.debug('Added new range_estimated_full %s for drive %s to database', element.value, self.drive.id)
200
+ self.last_range_estimated_full = new_range
201
+ except DatabaseError as err:
202
+ session.rollback()
203
+ LOG.error('DatabaseError while adding range_estimated_full for drive %s to database: %s', self.drive.id, err)
204
+ self.database_plugin.healthy._set_value(value=False) # pylint: disable=protected-access
205
+ elif self.last_range_estimated_full is not None and self.last_range_estimated_full.range_estimated_full == element.value \
206
+ and element.last_updated is not None:
207
+ if self.last_range_estimated_full.last_date is None or element.last_updated > self.last_range_estimated_full.last_date:
208
+ try:
209
+ self.last_range_estimated_full.last_date = element.last_updated
210
+ session.commit()
211
+ LOG.debug('Updated range_estimated_full %s for drive %s in database', element.value, self.drive.id)
212
+ except DatabaseError as err:
213
+ session.rollback()
214
+ LOG.error('DatabaseError while updating range_estimated_full for drive %s in database: %s', self.drive.id, err)
215
+ self.database_plugin.healthy._set_value(value=False) # pylint: disable=protected-access
216
+ self.session_factory.remove()
217
+
218
+ def __on_type_change(self, element: EnumAttribute[GenericDrive.Type], flags: Observable.ObserverEvent) -> None:
219
+ del flags
220
+ with self.type_lock:
221
+ with self.session_factory() as session:
222
+ self.drive = session.merge(self.drive)
223
+ session.refresh(self.drive)
224
+ if element.enabled and element.value is not None and self.drive.type != element.value:
225
+ try:
226
+ self.drive.type = element.value
227
+ session.commit()
228
+ except DatabaseError as err:
229
+ session.rollback()
230
+ LOG.error('DatabaseError while updating type for drive %s to database: %s', self.drive.id, err)
231
+ self.database_plugin.healthy._set_value(value=False) # pylint: disable=protected-access
232
+ self.session_factory.remove()
233
+
234
+ def __on_electric_total_capacity_change(self, element: EnergyAttribute, flags: Observable.ObserverEvent) -> None:
235
+ del flags
236
+ with self.total_capacity_lock:
237
+ with self.session_factory() as session:
238
+ self.drive = session.merge(self.drive)
239
+ session.refresh(self.drive)
240
+ if element.enabled and element.value is not None and self.drive.capacity_total != element.value:
241
+ try:
242
+ self.drive.capacity_total = element.value
243
+ session.commit()
244
+ except DatabaseError as err:
245
+ session.rollback()
246
+ LOG.error('DatabaseError while updating total capacity for drive %s to database: %s', self.drive.id, err)
247
+ self.database_plugin.healthy._set_value(value=False) # pylint: disable=protected-access
248
+ self.session_factory.remove()
249
+
250
+ def __on_electric_available_capacity_change(self, element: EnergyAttribute, flags: Observable.ObserverEvent) -> None:
251
+ del flags
252
+ with self.available_capacity_lock:
253
+ with self.session_factory() as session:
254
+ self.drive = session.merge(self.drive)
255
+ session.refresh(self.drive)
256
+ if element.enabled and element.value is not None and self.drive.capacity != element.value:
257
+ try:
258
+ self.drive.capacity = element.value
259
+ session.commit()
260
+ except DatabaseError as err:
261
+ session.rollback()
262
+ LOG.error('DatabaseError while updating available capacity for drive %s to database: %s', self.drive.id, err)
263
+ self.database_plugin.healthy._set_value(value=False) # pylint: disable=protected-access
264
+ self.session_factory.remove()
265
+
266
+ def __on_range_wltp_change(self, element: RangeAttribute, flags: Observable.ObserverEvent) -> None:
267
+ del flags
268
+ with self.range_wltp_lock:
269
+ with self.session_factory() as session:
270
+ self.drive = session.merge(self.drive)
271
+ session.refresh(self.drive)
272
+ if element.enabled and element.value is not None and self.drive.wltp_range != element.value:
273
+ try:
274
+ self.drive.wltp_range = element.value
275
+ session.commit()
276
+ except DatabaseError as err:
277
+ session.rollback()
278
+ LOG.error('DatabaseError while updating WLTP range for drive %s to database: %s', self.drive.id, err)
279
+ self.database_plugin.healthy._set_value(value=False) # pylint: disable=protected-access
280
+ self.session_factory.remove()
281
+
282
+ def __on_fuel_available_capacity_change(self, element: VolumeAttribute, flags: Observable.ObserverEvent) -> None:
283
+ del flags
284
+ with self.fuel_available_capacity_lock:
285
+ with self.session_factory() as session:
286
+ self.drive = session.merge(self.drive)
287
+ session.refresh(self.drive)
288
+ if element.enabled and element.value is not None and self.drive.capacity != element.value:
289
+ try:
290
+ self.drive.capacity = element.value
291
+ session.commit()
292
+ except DatabaseError as err:
293
+ session.rollback()
294
+ LOG.error('DatabaseError while updating available capacity for drive %s to database: %s', self.drive.id, err)
295
+ self.database_plugin.healthy._set_value(value=False) # pylint: disable=protected-access
296
+ self.session_factory.remove()
297
+
298
+ def __on_electric_consumption_change(self, element: EnergyConsumptionAttribute, flags: Observable.ObserverEvent) -> None:
299
+ del flags
300
+ if element.enabled:
301
+ with self.last_electric_consumption_lock:
302
+ with self.session_factory() as session:
303
+ self.drive = session.merge(self.drive)
304
+ session.refresh(self.drive)
305
+ if self.last_electric_consumption is not None:
306
+ self.last_electric_consumption = session.merge(self.last_electric_consumption)
307
+ session.refresh(self.last_electric_consumption)
308
+ if (self.last_electric_consumption is None or self.last_electric_consumption.consumption != element.value) \
309
+ and element.last_updated is not None:
310
+ new_level: DriveConsumption = DriveConsumption(drive_id=self.drive.id, first_date=element.last_updated, last_date=element.last_updated,
311
+ consumption=element.value)
312
+ try:
313
+ session.add(new_level)
314
+ session.commit()
315
+ LOG.debug('Added new consumption %s for drive %s to database', element.value, self.drive.id)
316
+ self.last_electric_consumption = new_level
317
+ except DatabaseError as err:
318
+ session.rollback()
319
+ LOG.error('DatabaseError while adding consumption for drive %s to database: %s', self.drive.id, err)
320
+ self.database_plugin.healthy._set_value(value=False) # pylint: disable=protected-access
321
+ elif self.last_electric_consumption is not None and self.last_electric_consumption.consumption == element.value \
322
+ and element.last_updated is not None:
323
+ if self.last_electric_consumption.last_date is None or element.last_updated > self.last_electric_consumption.last_date:
324
+ try:
325
+ self.last_electric_consumption.last_date = element.last_updated
326
+ session.commit()
327
+ LOG.debug('Updated consumption %s for drive %s in database', element.value, self.drive.id)
328
+ except DatabaseError as err:
329
+ session.rollback()
330
+ LOG.error('DatabaseError while updating consumption for drive %s in database: %s', self.drive.id, err)
331
+ self.database_plugin.healthy._set_value(value=False) # pylint: disable=protected-access
332
+ self.session_factory.remove()
333
+
334
+ def __on_fuel_consumption_change(self, element: FuelConsumptionAttribute, flags: Observable.ObserverEvent) -> None:
335
+ del flags
336
+ if element.enabled:
337
+ with self.last_fuel_consumption_lock:
338
+ with self.session_factory() as session:
339
+ self.drive = session.merge(self.drive)
340
+ session.refresh(self.drive)
341
+ if self.last_fuel_consumption is not None:
342
+ self.last_fuel_consumption = session.merge(self.last_fuel_consumption)
343
+ session.refresh(self.last_fuel_consumption)
344
+ if (self.last_fuel_consumption is None or self.last_fuel_consumption.consumption != element.value) \
345
+ and element.last_updated is not None:
346
+ new_level: DriveConsumption = DriveConsumption(drive_id=self.drive.id, first_date=element.last_updated, last_date=element.last_updated,
347
+ consumption=element.value)
348
+ try:
349
+ session.add(new_level)
350
+ session.commit()
351
+ LOG.debug('Added new consumption %s for drive %s to database', element.value, self.drive.id)
352
+ self.last_fuel_consumption = new_level
353
+ except DatabaseError as err:
354
+ session.rollback()
355
+ LOG.error('DatabaseError while adding consumption for drive %s to database: %s', self.drive.id, err)
356
+ self.database_plugin.healthy._set_value(value=False) # pylint: disable=protected-access
357
+ elif self.last_fuel_consumption is not None and self.last_fuel_consumption.consumption == element.value \
358
+ and element.last_updated is not None:
359
+ if self.last_fuel_consumption.last_date is None or element.last_updated > self.last_fuel_consumption.last_date:
360
+ try:
361
+ self.last_fuel_consumption.last_date = element.last_updated
362
+ session.commit()
363
+ LOG.debug('Updated consumption %s for drive %s in database', element.value, self.drive.id)
364
+ except DatabaseError as err:
365
+ session.rollback()
366
+ LOG.error('DatabaseError while updating consumption for drive %s in database: %s', self.drive.id, err)
367
+ self.database_plugin.healthy._set_value(value=False) # pylint: disable=protected-access
368
+ self.session_factory.remove()
@@ -0,0 +1,162 @@
1
+ from __future__ import annotations
2
+ from typing import TYPE_CHECKING
3
+
4
+ import threading
5
+
6
+ import logging
7
+
8
+ from sqlalchemy.exc import DatabaseError
9
+
10
+ from carconnectivity.observable import Observable
11
+
12
+ from carconnectivity_plugins.database.agents.base_agent import BaseAgent
13
+ from carconnectivity_plugins.database.model.state import State
14
+ from carconnectivity_plugins.database.model.connection_state import ConnectionState
15
+ from carconnectivity_plugins.database.model.outside_temperature import OutsideTemperature
16
+
17
+ if TYPE_CHECKING:
18
+ from typing import Optional
19
+ from sqlalchemy.orm import scoped_session
20
+ from sqlalchemy.orm.session import Session
21
+
22
+ from carconnectivity.attributes import EnumAttribute, TemperatureAttribute
23
+
24
+ from carconnectivity.vehicle import GenericVehicle
25
+
26
+ from carconnectivity_plugins.database.plugin import Plugin
27
+ from carconnectivity_plugins.database.model.vehicle import Vehicle
28
+
29
+
30
+ LOG: logging.Logger = logging.getLogger("carconnectivity.plugins.database.agents.state_agent")
31
+
32
+
33
+ class StateAgent(BaseAgent):
34
+ def __init__(self, database_plugin: Plugin, session_factory: scoped_session[Session], vehicle: Vehicle) -> None:
35
+ if vehicle is None or vehicle.carconnectivity_vehicle is None:
36
+ raise ValueError("Vehicle or its carconnectivity_vehicle attribute is None")
37
+ self.database_plugin: Plugin = database_plugin
38
+ self.session_factory: scoped_session[Session] = session_factory
39
+ self.vehicle: Vehicle = vehicle
40
+
41
+ with self.session_factory() as session:
42
+ self.last_state: Optional[State] = session.query(State).filter(State.vehicle == vehicle).order_by(State.first_date.desc()).first()
43
+ self.last_state_lock: threading.RLock = threading.RLock()
44
+
45
+ self.last_connection_state: Optional[ConnectionState] = session.query(ConnectionState).filter(ConnectionState.vehicle == vehicle) \
46
+ .order_by(ConnectionState.first_date.desc()).first()
47
+ self.last_connection_state_lock: threading.RLock = threading.RLock()
48
+
49
+ self.last_outside_temperature: Optional[OutsideTemperature] = session.query(OutsideTemperature).filter(OutsideTemperature.vehicle == vehicle) \
50
+ .order_by(OutsideTemperature.first_date.desc()).first()
51
+ self.last_outside_temperature_lock: threading.RLock = threading.RLock()
52
+
53
+ vehicle.carconnectivity_vehicle.state.add_observer(self.__on_state_change, Observable.ObserverEvent.UPDATED)
54
+ self.__on_state_change(vehicle.carconnectivity_vehicle.state, Observable.ObserverEvent.UPDATED)
55
+
56
+ vehicle.carconnectivity_vehicle.connection_state.add_observer(self.__on_connection_state_change, Observable.ObserverEvent.UPDATED)
57
+ self.__on_connection_state_change(vehicle.carconnectivity_vehicle.connection_state, Observable.ObserverEvent.UPDATED)
58
+
59
+ vehicle.carconnectivity_vehicle.outside_temperature.add_observer(self.__on_outside_temperature_change, Observable.ObserverEvent.UPDATED)
60
+ self.__on_outside_temperature_change(vehicle.carconnectivity_vehicle.outside_temperature, Observable.ObserverEvent.UPDATED)
61
+ self.session_factory.remove()
62
+
63
+ def __on_state_change(self, element: EnumAttribute[GenericVehicle.State], flags: Observable.ObserverEvent) -> None:
64
+ del flags
65
+ if element.enabled:
66
+ with self.last_state_lock:
67
+ with self.session_factory() as session:
68
+ if self.last_state is not None:
69
+ self.last_state = session.merge(self.last_state)
70
+ session.refresh(self.last_state)
71
+ if (self.last_state is None or self.last_state.state != element.value) \
72
+ and element.last_updated is not None:
73
+ new_state: State = State(vin=self.vehicle.vin, first_date=element.last_updated, last_date=element.last_updated, state=element.value)
74
+ try:
75
+ session.add(new_state)
76
+ session.commit()
77
+ LOG.debug('Added new state %s for vehicle %s to database', element.value, self.vehicle.vin)
78
+ self.last_state = new_state
79
+ except DatabaseError as err:
80
+ session.rollback()
81
+ LOG.error('DatabaseError while adding state for vehicle %s to database: %s', self.vehicle.vin, err)
82
+ self.database_plugin.healthy._set_value(value=False) # pylint: disable=protected-access
83
+
84
+ elif self.last_state is not None and self.last_state.state == element.value and element.last_updated is not None:
85
+ if self.last_state.last_date is None or element.last_updated > self.last_state.last_date:
86
+ try:
87
+ self.last_state.last_date = element.last_updated
88
+ session.commit()
89
+ LOG.debug('Updated state %s for vehicle %s in database', element.value, self.vehicle.vin)
90
+ except DatabaseError as err:
91
+ session.rollback()
92
+ LOG.error('DatabaseError while updating state for vehicle %s in database: %s', self.vehicle.vin, err)
93
+ self.database_plugin.healthy._set_value(value=False) # pylint: disable=protected-access
94
+ self.session_factory.remove()
95
+
96
+ def __on_connection_state_change(self, element: EnumAttribute[GenericVehicle.ConnectionState], flags: Observable.ObserverEvent) -> None:
97
+ del flags
98
+ if element.enabled:
99
+ with self.last_connection_state_lock:
100
+ with self.session_factory() as session:
101
+ if self.last_connection_state is not None:
102
+ self.last_connection_state = session.merge(self.last_connection_state)
103
+ session.refresh(self.last_connection_state)
104
+ if (self.last_connection_state is None or self.last_connection_state.connection_state != element.value) \
105
+ and element.last_updated is not None:
106
+ new_connection_state: ConnectionState = ConnectionState(vin=self.vehicle.vin, first_date=element.last_updated,
107
+ last_date=element.last_updated, connection_state=element.value)
108
+ try:
109
+ session.add(new_connection_state)
110
+ session.commit()
111
+ LOG.debug('Added new connection state %s for vehicle %s to database', element.value, self.vehicle.vin)
112
+ self.last_connection_state = new_connection_state
113
+ except DatabaseError as err:
114
+ session.rollback()
115
+ LOG.error('DatabaseError while adding connection state for vehicle %s to database: %s', self.vehicle.vin, err)
116
+ self.database_plugin.healthy._set_value(value=False) # pylint: disable=protected-access
117
+ elif self.last_connection_state is not None and self.last_connection_state.connection_state == element.value \
118
+ and element.last_updated is not None:
119
+ if self.last_connection_state.last_date is None or element.last_updated > self.last_connection_state.last_date:
120
+ try:
121
+ self.last_connection_state.last_date = element.last_updated
122
+ session.commit()
123
+ LOG.debug('Updated connection state %s for vehicle %s in database', element.value, self.vehicle.vin)
124
+ except DatabaseError as err:
125
+ session.rollback()
126
+ LOG.error('DatabaseError while updating connection state for vehicle %s in database: %s', self.vehicle.vin, err)
127
+ self.database_plugin.healthy._set_value(value=False) # pylint: disable=protected-access
128
+ self.session_factory.remove()
129
+
130
+ def __on_outside_temperature_change(self, element: TemperatureAttribute, flags: Observable.ObserverEvent) -> None:
131
+ del flags
132
+ if element.enabled:
133
+ with self.last_outside_temperature_lock:
134
+ with self.session_factory() as session:
135
+ if self.last_outside_temperature is not None:
136
+ self.last_outside_temperature = session.merge(self.last_outside_temperature)
137
+ session.refresh(self.last_outside_temperature)
138
+ if (self.last_outside_temperature is None or self.last_outside_temperature.outside_temperature != element.value) \
139
+ and element.last_updated is not None:
140
+ new_outside_temperature: OutsideTemperature = OutsideTemperature(vin=self.vehicle.vin, first_date=element.last_updated,
141
+ last_date=element.last_updated, outside_temperature=element.value)
142
+ try:
143
+ session.add(new_outside_temperature)
144
+ session.commit()
145
+ LOG.debug('Added new outside temperature %.2f for vehicle %s to database', element.value, self.vehicle.vin)
146
+ self.last_outside_temperature = new_outside_temperature
147
+ except DatabaseError as err:
148
+ session.rollback()
149
+ LOG.error('DatabaseError while adding outside temperature for vehicle %s to database: %s', self.vehicle.vin, err)
150
+ self.database_plugin.healthy._set_value(value=False) # pylint: disable=protected-access
151
+ elif self.last_outside_temperature is not None and self.last_outside_temperature.outside_temperature == element.value \
152
+ and element.last_updated is not None:
153
+ if self.last_outside_temperature.last_date is None or element.last_updated > self.last_outside_temperature.last_date:
154
+ try:
155
+ self.last_outside_temperature.last_date = element.last_updated
156
+ session.commit()
157
+ LOG.debug('Updated outside temperature %.2f for vehicle %s in database', element.value, self.vehicle.vin)
158
+ except DatabaseError as err:
159
+ session.rollback()
160
+ LOG.error('DatabaseError while updating outside temperature for vehicle %s in database: %s', self.vehicle.vin, err)
161
+ self.database_plugin.healthy._set_value(value=False) # pylint: disable=protected-access
162
+ self.session_factory.remove()