carconnectivity-plugin-database 0.1__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.

Potentially problematic release.


This version of carconnectivity-plugin-database might be problematic. Click here for more details.

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