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.
- carconnectivity_database/__init__.py +0 -0
- carconnectivity_database/carconnectivity_database_base.py +26 -0
- carconnectivity_plugin_database-0.1a19.dist-info/METADATA +74 -0
- carconnectivity_plugin_database-0.1a19.dist-info/RECORD +48 -0
- carconnectivity_plugin_database-0.1a19.dist-info/WHEEL +5 -0
- carconnectivity_plugin_database-0.1a19.dist-info/entry_points.txt +2 -0
- carconnectivity_plugin_database-0.1a19.dist-info/licenses/LICENSE +21 -0
- carconnectivity_plugin_database-0.1a19.dist-info/top_level.txt +2 -0
- carconnectivity_plugins/database/__init__.py +0 -0
- carconnectivity_plugins/database/_version.py +34 -0
- carconnectivity_plugins/database/agents/base_agent.py +2 -0
- carconnectivity_plugins/database/agents/charging_agent.py +523 -0
- carconnectivity_plugins/database/agents/climatization_agent.py +80 -0
- carconnectivity_plugins/database/agents/drive_state_agent.py +368 -0
- carconnectivity_plugins/database/agents/state_agent.py +162 -0
- carconnectivity_plugins/database/agents/trip_agent.py +225 -0
- carconnectivity_plugins/database/model/__init__.py +19 -0
- carconnectivity_plugins/database/model/alembic.ini +100 -0
- carconnectivity_plugins/database/model/base.py +4 -0
- carconnectivity_plugins/database/model/carconnectivity_schema/README +1 -0
- carconnectivity_plugins/database/model/carconnectivity_schema/__init__.py +0 -0
- carconnectivity_plugins/database/model/carconnectivity_schema/env.py +78 -0
- carconnectivity_plugins/database/model/carconnectivity_schema/script.py.mako +24 -0
- carconnectivity_plugins/database/model/carconnectivity_schema/versions/__init__.py +0 -0
- carconnectivity_plugins/database/model/charging_power.py +52 -0
- carconnectivity_plugins/database/model/charging_rate.py +52 -0
- carconnectivity_plugins/database/model/charging_session.py +144 -0
- carconnectivity_plugins/database/model/charging_state.py +57 -0
- carconnectivity_plugins/database/model/charging_station.py +66 -0
- carconnectivity_plugins/database/model/climatization_state.py +57 -0
- carconnectivity_plugins/database/model/connection_state.py +57 -0
- carconnectivity_plugins/database/model/datetime_decorator.py +44 -0
- carconnectivity_plugins/database/model/drive.py +89 -0
- carconnectivity_plugins/database/model/drive_consumption.py +48 -0
- carconnectivity_plugins/database/model/drive_level.py +50 -0
- carconnectivity_plugins/database/model/drive_range.py +53 -0
- carconnectivity_plugins/database/model/drive_range_full.py +53 -0
- carconnectivity_plugins/database/model/location.py +85 -0
- carconnectivity_plugins/database/model/migrations.py +24 -0
- carconnectivity_plugins/database/model/outside_temperature.py +52 -0
- carconnectivity_plugins/database/model/state.py +56 -0
- carconnectivity_plugins/database/model/tag.py +31 -0
- carconnectivity_plugins/database/model/timedelta_decorator.py +33 -0
- carconnectivity_plugins/database/model/trip.py +85 -0
- carconnectivity_plugins/database/model/vehicle.py +186 -0
- carconnectivity_plugins/database/plugin.py +167 -0
- carconnectivity_plugins/database/ui/plugin_ui.py +48 -0
- 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()
|