carconnectivity-plugin-database 0.1a16__py3-none-any.whl → 0.1a21__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.

@@ -4,7 +4,7 @@ from typing import TYPE_CHECKING
4
4
  import threading
5
5
 
6
6
  import logging
7
- from datetime import timedelta
7
+ from datetime import timedelta, datetime, timezone
8
8
 
9
9
  from sqlalchemy.exc import DatabaseError
10
10
 
@@ -27,8 +27,9 @@ if TYPE_CHECKING:
27
27
  from sqlalchemy.orm import scoped_session
28
28
  from sqlalchemy.orm.session import Session
29
29
 
30
- from carconnectivity.attributes import EnumAttribute, SpeedAttribute, PowerAttribute
30
+ from carconnectivity.attributes import EnumAttribute, SpeedAttribute, PowerAttribute, FloatAttribute
31
31
 
32
+ from carconnectivity_plugins.database.plugin import Plugin
32
33
  from carconnectivity_plugins.database.model.vehicle import Vehicle
33
34
  from carconnectivity.drive import ElectricDrive
34
35
 
@@ -37,13 +38,16 @@ LOG: logging.Logger = logging.getLogger("carconnectivity.plugins.database.agents
37
38
 
38
39
  class ChargingAgent(BaseAgent):
39
40
 
40
- def __init__(self, session_factory: scoped_session[Session], vehicle: Vehicle) -> None:
41
- if vehicle is None or vehicle.carconnectivity_vehicle is None:
41
+ def __init__(self, database_plugin: Plugin, session_factory: scoped_session[Session], vehicle: Vehicle,
42
+ carconnectivity_vehicle: ElectricVehicle) -> None:
43
+ if vehicle is None or carconnectivity_vehicle is None:
42
44
  raise ValueError("Vehicle or its carconnectivity_vehicle attribute is None")
43
- if not isinstance(vehicle.carconnectivity_vehicle, ElectricVehicle):
45
+ if not isinstance(carconnectivity_vehicle, ElectricVehicle):
44
46
  raise ValueError("Vehicle's carconnectivity_vehicle attribute is not an ElectricVehicle")
47
+ self.database_plugin: Plugin = database_plugin
45
48
  self.session_factory: scoped_session[Session] = session_factory
46
49
  self.vehicle: Vehicle = vehicle
50
+ self.carconnectivity_vehicle: ElectricVehicle = carconnectivity_vehicle
47
51
 
48
52
  with self.session_factory() as session:
49
53
  self.last_charging_session: Optional[ChargingSession] = session.query(ChargingSession).filter(ChargingSession.vehicle == vehicle) \
@@ -52,24 +56,22 @@ class ChargingAgent(BaseAgent):
52
56
  ChargingSession.plug_connected_date.desc().nulls_first()).first()
53
57
 
54
58
  self.last_charging_session_lock: threading.RLock = threading.RLock()
55
- self.carconnectivity_last_charging_state: Optional[Charging.ChargingState] = vehicle.carconnectivity_vehicle.charging.state.value
56
- self.carconnectivity_last_connector_state: Optional[ChargingConnector.ChargingConnectorConnectionState] = vehicle.carconnectivity_vehicle.charging\
59
+ self.carconnectivity_last_charging_state: Optional[Charging.ChargingState] = self.carconnectivity_vehicle.charging.state.value
60
+ self.carconnectivity_last_connector_state: Optional[ChargingConnector.ChargingConnectorConnectionState] = self.carconnectivity_vehicle.charging\
57
61
  .connector.connection_state.value
58
- self.carconnectivity_last_connector_lock_state: Optional[ChargingConnector.ChargingConnectorLockState] = vehicle.carconnectivity_vehicle.charging\
62
+ self.carconnectivity_last_connector_lock_state: Optional[ChargingConnector.ChargingConnectorLockState] = self.carconnectivity_vehicle.charging\
59
63
  .connector.lock_state.value
60
64
  if self.last_charging_session is not None and not self.last_charging_session.is_closed():
61
- if vehicle.carconnectivity_vehicle.charging.state.value in (Charging.ChargingState.CHARGING, Charging.ChargingState.CONSERVATION) \
62
- or (vehicle.carconnectivity_vehicle.charging.connector.connection_state.enabled
63
- and vehicle.carconnectivity_vehicle.charging.connector.connection_state.value ==
65
+ if self.carconnectivity_vehicle.charging.state.value in (Charging.ChargingState.CHARGING, Charging.ChargingState.CONSERVATION) \
66
+ or (self.carconnectivity_vehicle.charging.connector.connection_state.enabled
67
+ and self.carconnectivity_vehicle.charging.connector.connection_state.value ==
64
68
  ChargingConnector.ChargingConnectorConnectionState.CONNECTED) \
65
- or (vehicle.carconnectivity_vehicle.charging.connector.lock_state.enabled
66
- and vehicle.carconnectivity_vehicle.charging.connector.lock_state.value == ChargingConnector.ChargingConnectorLockState.LOCKED):
69
+ or (self.carconnectivity_vehicle.charging.connector.lock_state.enabled
70
+ and self.carconnectivity_vehicle.charging.connector.lock_state.value == ChargingConnector.ChargingConnectorLockState.LOCKED):
67
71
  LOG.info("Last charging session for vehicle %s is still open during startup, will continue this session", vehicle.vin)
68
72
  else:
69
73
  LOG.info("Last charging session for vehicle %s is still open during startup, but we are not charging, ignoring it", vehicle.vin)
70
74
  self.last_charging_session = None
71
- else:
72
- self.last_charging_session = None
73
75
 
74
76
  self.last_charging_state: Optional[ChargingState] = session.query(ChargingState).filter(ChargingState.vehicle == vehicle)\
75
77
  .order_by(ChargingState.first_date.desc()).first()
@@ -83,30 +85,36 @@ class ChargingAgent(BaseAgent):
83
85
  .order_by(ChargingPower.first_date.desc()).first()
84
86
  self.last_charging_power_lock: threading.RLock = threading.RLock()
85
87
 
86
- vehicle.carconnectivity_vehicle.charging.connector.connection_state.add_observer(self.__on_connector_state_change, Observable.ObserverEvent.UPDATED)
87
- if vehicle.carconnectivity_vehicle.charging.connector.connection_state.enabled:
88
- self.__on_connector_state_change(vehicle.carconnectivity_vehicle.charging.connector.connection_state, Observable.ObserverEvent.UPDATED)
88
+ self.carconnectivity_vehicle.charging.connector.connection_state.add_observer(self.__on_connector_state_change, Observable.ObserverEvent.UPDATED)
89
+ if self.carconnectivity_vehicle.charging.connector.connection_state.enabled:
90
+ self.__on_connector_state_change(self.carconnectivity_vehicle.charging.connector.connection_state, Observable.ObserverEvent.UPDATED)
89
91
 
90
- vehicle.carconnectivity_vehicle.charging.connector.lock_state.add_observer(self.__on_connector_lock_state_change, Observable.ObserverEvent.UPDATED)
91
- if vehicle.carconnectivity_vehicle.charging.connector.lock_state.enabled:
92
- self.__on_connector_lock_state_change(vehicle.carconnectivity_vehicle.charging.connector.lock_state, Observable.ObserverEvent.UPDATED)
92
+ self.carconnectivity_vehicle.charging.connector.lock_state.add_observer(self.__on_connector_lock_state_change, Observable.ObserverEvent.UPDATED)
93
+ if self.carconnectivity_vehicle.charging.connector.lock_state.enabled:
94
+ self.__on_connector_lock_state_change(self.carconnectivity_vehicle.charging.connector.lock_state, Observable.ObserverEvent.UPDATED)
93
95
 
94
- vehicle.carconnectivity_vehicle.charging.state.add_observer(self.__on_charging_state_change, Observable.ObserverEvent.UPDATED)
95
- if vehicle.carconnectivity_vehicle.charging.state.enabled:
96
- self.__on_charging_state_change(vehicle.carconnectivity_vehicle.charging.state, Observable.ObserverEvent.UPDATED)
96
+ self.carconnectivity_vehicle.charging.state.add_observer(self.__on_charging_state_change, Observable.ObserverEvent.UPDATED)
97
+ if self.carconnectivity_vehicle.charging.state.enabled:
98
+ self.__on_charging_state_change(self.carconnectivity_vehicle.charging.state, Observable.ObserverEvent.UPDATED)
97
99
 
98
- vehicle.carconnectivity_vehicle.charging.rate.add_observer(self.__on_charging_rate_change, Observable.ObserverEvent.UPDATED)
99
- if vehicle.carconnectivity_vehicle.charging.rate.enabled:
100
- self.__on_charging_rate_change(vehicle.carconnectivity_vehicle.charging.rate, Observable.ObserverEvent.UPDATED)
100
+ self.carconnectivity_vehicle.charging.rate.add_observer(self.__on_charging_rate_change, Observable.ObserverEvent.UPDATED)
101
+ if self.carconnectivity_vehicle.charging.rate.enabled:
102
+ self.__on_charging_rate_change(self.carconnectivity_vehicle.charging.rate, Observable.ObserverEvent.UPDATED)
101
103
 
102
- vehicle.carconnectivity_vehicle.charging.power.add_observer(self.__on_charging_power_change, Observable.ObserverEvent.UPDATED)
103
- if vehicle.carconnectivity_vehicle.charging.power.enabled:
104
- self.__on_charging_power_change(vehicle.carconnectivity_vehicle.charging.power, Observable.ObserverEvent.UPDATED)
104
+ self.carconnectivity_vehicle.charging.power.add_observer(self.__on_charging_power_change, Observable.ObserverEvent.UPDATED)
105
+ if self.carconnectivity_vehicle.charging.power.enabled:
106
+ self.__on_charging_power_change(self.carconnectivity_vehicle.charging.power, Observable.ObserverEvent.UPDATED)
107
+
108
+ self.carconnectivity_vehicle.charging.type.add_observer(self._on_charging_type_change, Observable.ObserverEvent.VALUE_CHANGED)
109
+
110
+ electric_drive: Optional[ElectricDrive] = self.carconnectivity_vehicle.get_electric_drive()
111
+ if electric_drive is not None:
112
+ electric_drive.level.add_observer(self._on_battery_level_change, Observable.ObserverEvent.VALUE_CHANGED)
105
113
  self.session_factory.remove()
106
114
 
107
115
  def __on_charging_state_change(self, element: EnumAttribute[Charging.ChargingState], flags: Observable.ObserverEvent) -> None:
108
116
  del flags
109
- if self.vehicle.carconnectivity_vehicle is None:
117
+ if self.carconnectivity_vehicle is None:
110
118
  raise ValueError("Vehicle's carconnectivity_vehicle attribute is None")
111
119
 
112
120
  if element.enabled:
@@ -115,8 +123,9 @@ class ChargingAgent(BaseAgent):
115
123
  if self.last_charging_state is not None:
116
124
  self.last_charging_state = session.merge(self.last_charging_state)
117
125
  session.refresh(self.last_charging_state)
118
- if (self.last_charging_state is None or self.last_charging_state.state != element.value) \
119
- and element.last_updated is not None:
126
+ if element.last_updated is not None \
127
+ and (self.last_charging_state is None or (self.last_charging_state.state != element.value
128
+ and element.last_updated > self.last_charging_state.last_date)):
120
129
  new_charging_state: ChargingState = ChargingState(vin=self.vehicle.vin, first_date=element.last_updated,
121
130
  last_date=element.last_updated, state=element.value)
122
131
  try:
@@ -127,6 +136,7 @@ class ChargingAgent(BaseAgent):
127
136
  except DatabaseError as err:
128
137
  session.rollback()
129
138
  LOG.error('DatabaseError while adding charging state for vehicle %s to database: %s', self.vehicle.vin, err)
139
+ self.database_plugin.healthy._set_value(value=False) # pylint: disable=protected-access
130
140
 
131
141
  elif self.last_charging_state is not None and self.last_charging_state.state == element.value and element.last_updated is not None:
132
142
  if self.last_charging_state.last_date is None or element.last_updated > self.last_charging_state.last_date:
@@ -137,6 +147,7 @@ class ChargingAgent(BaseAgent):
137
147
  except DatabaseError as err:
138
148
  session.rollback()
139
149
  LOG.error('DatabaseError while updating charging state for vehicle %s in database: %s', self.vehicle.vin, err)
150
+ self.database_plugin.healthy._set_value(value=False) # pylint: disable=protected-access
140
151
 
141
152
  with self.last_charging_session_lock:
142
153
  if self.last_charging_session is not None:
@@ -161,10 +172,12 @@ class ChargingAgent(BaseAgent):
161
172
  try:
162
173
  self.last_charging_session.session_end_date = None
163
174
  self.last_charging_session.end_level = None
175
+ self._update_session_charging_type(session, self.last_charging_session)
164
176
  session.commit()
165
177
  except DatabaseError as err:
166
178
  session.rollback()
167
179
  LOG.error('DatabaseError while updating charging session for vehicle %s in database: %s', self.vehicle.vin, err)
180
+ self.database_plugin.healthy._set_value(value=False) # pylint: disable=protected-access
168
181
  else:
169
182
  LOG.info("Starting new charging session for vehicle %s", self.vehicle.vin)
170
183
  new_session: ChargingSession = ChargingSession(vin=self.vehicle.vin, session_start_date=element.last_changed)
@@ -179,6 +192,7 @@ class ChargingAgent(BaseAgent):
179
192
  except DatabaseError as err:
180
193
  session.rollback()
181
194
  LOG.error('DatabaseError while adding charging session for vehicle %s to database: %s', self.vehicle.vin, err)
195
+ self.database_plugin.healthy._set_value(value=False) # pylint: disable=protected-access
182
196
  else:
183
197
  if self.last_charging_session.was_started():
184
198
  LOG.debug("Continuing existing charging session for vehicle %s", self.vehicle.vin)
@@ -193,9 +207,10 @@ class ChargingAgent(BaseAgent):
193
207
  except DatabaseError as err:
194
208
  session.rollback()
195
209
  LOG.error('DatabaseError while starting charging session for vehicle %s in database: %s', self.vehicle.vin, err)
210
+ self.database_plugin.healthy._set_value(value=False) # pylint: disable=protected-access
196
211
  # Update startlevel at beginning of charging
197
- if self.last_charging_session is not None and isinstance(self.vehicle.carconnectivity_vehicle, ElectricVehicle):
198
- electric_drive: Optional[ElectricDrive] = self.vehicle.carconnectivity_vehicle.get_electric_drive()
212
+ if self.last_charging_session is not None and isinstance(self.carconnectivity_vehicle, ElectricVehicle):
213
+ electric_drive: Optional[ElectricDrive] = self.carconnectivity_vehicle.get_electric_drive()
199
214
  if electric_drive is not None and electric_drive.level.enabled and electric_drive.level.value is not None:
200
215
  try:
201
216
  self.last_charging_session.start_level = electric_drive.level.value
@@ -203,6 +218,7 @@ class ChargingAgent(BaseAgent):
203
218
  except DatabaseError as err:
204
219
  session.rollback()
205
220
  LOG.error('DatabaseError while setting start level for vehicle %s in database: %s', self.vehicle.vin, err)
221
+ self.database_plugin.healthy._set_value(value=False) # pylint: disable=protected-access
206
222
  elif element.value not in (Charging.ChargingState.CHARGING, Charging.ChargingState.CONSERVATION) \
207
223
  and self.carconnectivity_last_charging_state in (Charging.ChargingState.CHARGING, Charging.ChargingState.CONSERVATION):
208
224
  if self.last_charging_session is not None and not self.last_charging_session.was_ended():
@@ -213,8 +229,9 @@ class ChargingAgent(BaseAgent):
213
229
  except DatabaseError as err:
214
230
  session.rollback()
215
231
  LOG.error('DatabaseError while ending charging session for vehicle %s in database: %s', self.vehicle.vin, err)
216
- if isinstance(self.vehicle.carconnectivity_vehicle, ElectricVehicle):
217
- electric_drive: Optional[ElectricDrive] = self.vehicle.carconnectivity_vehicle.get_electric_drive()
232
+ self.database_plugin.healthy._set_value(value=False) # pylint: disable=protected-access
233
+ if isinstance(self.carconnectivity_vehicle, ElectricVehicle):
234
+ electric_drive: Optional[ElectricDrive] = self.carconnectivity_vehicle.get_electric_drive()
218
235
  if electric_drive is not None and electric_drive.level.enabled and electric_drive.level.value is not None:
219
236
  try:
220
237
  self.last_charging_session.end_level = electric_drive.level.value
@@ -222,12 +239,13 @@ class ChargingAgent(BaseAgent):
222
239
  except DatabaseError as err:
223
240
  session.rollback()
224
241
  LOG.error('DatabaseError while setting start level for vehicle %s in database: %s', self.vehicle.vin, err)
242
+ self.database_plugin.healthy._set_value(value=False) # pylint: disable=protected-access
225
243
  self.carconnectivity_last_charging_state = element.value
226
244
  self.session_factory.remove()
227
245
 
228
246
  def __on_charging_rate_change(self, element: SpeedAttribute, flags: Observable.ObserverEvent) -> None:
229
247
  del flags
230
- if self.vehicle.carconnectivity_vehicle is None:
248
+ if self.carconnectivity_vehicle is None:
231
249
  raise ValueError("Vehicle's carconnectivity_vehicle attribute is None")
232
250
  if element.enabled:
233
251
  with self.last_charging_rate_lock:
@@ -235,8 +253,9 @@ class ChargingAgent(BaseAgent):
235
253
  if self.last_charging_rate is not None:
236
254
  self.last_charging_rate = session.merge(self.last_charging_rate)
237
255
  session.refresh(self.last_charging_rate)
238
- if (self.last_charging_rate is None or self.last_charging_rate.rate != element.value) \
239
- and element.last_updated is not None:
256
+ if element.last_updated is not None \
257
+ and (self.last_charging_rate is None or (self.last_charging_rate.rate != element.value
258
+ and element.last_updated > self.last_charging_rate.last_date)):
240
259
  new_charging_rate: ChargingRate = ChargingRate(vin=self.vehicle.vin, first_date=element.last_updated,
241
260
  last_date=element.last_updated, rate=element.value)
242
261
  try:
@@ -247,6 +266,7 @@ class ChargingAgent(BaseAgent):
247
266
  except DatabaseError as err:
248
267
  session.rollback()
249
268
  LOG.error('DatabaseError while adding charging rate for vehicle %s to database: %s', self.vehicle.vin, err)
269
+ self.database_plugin.healthy._set_value(value=False) # pylint: disable=protected-access
250
270
  elif self.last_charging_rate is not None and self.last_charging_rate.rate == element.value and element.last_updated is not None:
251
271
  if self.last_charging_rate.last_date is None or element.last_updated > self.last_charging_rate.last_date:
252
272
  try:
@@ -256,11 +276,12 @@ class ChargingAgent(BaseAgent):
256
276
  except DatabaseError as err:
257
277
  session.rollback()
258
278
  LOG.error('DatabaseError while updating charging rate for vehicle %s in database: %s', self.vehicle.vin, err)
279
+ self.database_plugin.healthy._set_value(value=False) # pylint: disable=protected-access
259
280
  self.session_factory.remove()
260
281
 
261
282
  def __on_charging_power_change(self, element: PowerAttribute, flags: Observable.ObserverEvent) -> None:
262
283
  del flags
263
- if self.vehicle.carconnectivity_vehicle is None:
284
+ if self.carconnectivity_vehicle is None:
264
285
  raise ValueError("Vehicle's carconnectivity_vehicle attribute is None")
265
286
  if element.enabled:
266
287
  with self.last_charging_power_lock:
@@ -268,8 +289,9 @@ class ChargingAgent(BaseAgent):
268
289
  if self.last_charging_power is not None:
269
290
  self.last_charging_power = session.merge(self.last_charging_power)
270
291
  session.refresh(self.last_charging_power)
271
- if (self.last_charging_power is None or self.last_charging_power.power != element.value) \
272
- and element.last_updated is not None:
292
+ if element.last_updated is not None \
293
+ and (self.last_charging_power is None or (self.last_charging_power.power != element.value
294
+ and element.last_updated > self.last_charging_power.last_date)):
273
295
  new_charging_power: ChargingPower = ChargingPower(vin=self.vehicle.vin, first_date=element.last_updated,
274
296
  last_date=element.last_updated, power=element.value)
275
297
  try:
@@ -280,6 +302,7 @@ class ChargingAgent(BaseAgent):
280
302
  except DatabaseError as err:
281
303
  session.rollback()
282
304
  LOG.error('DatabaseError while adding charging power for vehicle %s to database: %s', self.vehicle.vin, err)
305
+ self.database_plugin.healthy._set_value(value=False) # pylint: disable=protected-access
283
306
  elif self.last_charging_power is not None and self.last_charging_power.power == element.value and element.last_updated is not None:
284
307
  if self.last_charging_power.last_date is None or element.last_updated > self.last_charging_power.last_date:
285
308
  try:
@@ -288,11 +311,12 @@ class ChargingAgent(BaseAgent):
288
311
  except DatabaseError as err:
289
312
  session.rollback()
290
313
  LOG.error('DatabaseError while updating charging power for vehicle %s in database: %s', self.vehicle.vin, err)
314
+ self.database_plugin.healthy._set_value(value=False) # pylint: disable=protected-access
291
315
  self.session_factory.remove()
292
316
 
293
317
  def __on_connector_state_change(self, element: EnumAttribute[ChargingConnector.ChargingConnectorConnectionState], flags: Observable.ObserverEvent) -> None:
294
318
  del flags
295
- if self.vehicle.carconnectivity_vehicle is None:
319
+ if self.carconnectivity_vehicle is None:
296
320
  raise ValueError("Vehicle's carconnectivity_vehicle attribute is None")
297
321
 
298
322
  with self.session_factory() as session:
@@ -302,6 +326,7 @@ class ChargingAgent(BaseAgent):
302
326
  session.refresh(self.last_charging_session)
303
327
 
304
328
  if element.value == ChargingConnector.ChargingConnectorConnectionState.CONNECTED \
329
+ and self.carconnectivity_last_connector_state is not None \
305
330
  and self.carconnectivity_last_connector_state != ChargingConnector.ChargingConnectorConnectionState.CONNECTED:
306
331
  if self.last_charging_session is None or self.last_charging_session.is_closed():
307
332
  LOG.info("Starting new charging session for vehicle %s due to connector connected state", self.vehicle.vin)
@@ -316,6 +341,7 @@ class ChargingAgent(BaseAgent):
316
341
  except DatabaseError as err:
317
342
  session.rollback()
318
343
  LOG.error('DatabaseError while adding charging session for vehicle %s to database: %s', self.vehicle.vin, err)
344
+ self.database_plugin.healthy._set_value(value=False) # pylint: disable=protected-access
319
345
  elif not self.last_charging_session.was_connected():
320
346
  LOG.debug("Continuing existing charging session for vehicle %s, writing connected date", self.vehicle.vin)
321
347
  try:
@@ -326,6 +352,7 @@ class ChargingAgent(BaseAgent):
326
352
  except DatabaseError as err:
327
353
  session.rollback()
328
354
  LOG.error('DatabaseError while starting charging session for vehicle %s in database: %s', self.vehicle.vin, err)
355
+ self.database_plugin.healthy._set_value(value=False) # pylint: disable=protected-access
329
356
  elif element.value != ChargingConnector.ChargingConnectorConnectionState.CONNECTED \
330
357
  and self.carconnectivity_last_connector_state == ChargingConnector.ChargingConnectorConnectionState.CONNECTED:
331
358
  if self.last_charging_session is not None and not self.last_charging_session.was_disconnected():
@@ -336,22 +363,29 @@ class ChargingAgent(BaseAgent):
336
363
  except DatabaseError as err:
337
364
  session.rollback()
338
365
  LOG.error('DatabaseError while ending charging session for vehicle %s in database: %s', self.vehicle.vin, err)
366
+ self.database_plugin.healthy._set_value(value=False) # pylint: disable=protected-access
339
367
  # Create charging session when connected at startup
340
368
  elif element.value == ChargingConnector.ChargingConnectorConnectionState.CONNECTED \
341
369
  and self.carconnectivity_last_connector_state == ChargingConnector.ChargingConnectorConnectionState.CONNECTED:
342
370
  if self.last_charging_session is None or self.last_charging_session.is_closed():
343
- LOG.info("Starting new charging session for vehicle %s due to connector connected state", self.vehicle.vin)
344
- new_session: ChargingSession = ChargingSession(vin=self.vehicle.vin, plug_connected_date=element.last_changed)
345
- try:
346
- session.add(new_session)
347
- self._update_session_odometer(session, new_session)
348
- self._update_session_position(session, new_session)
349
- session.commit()
350
- LOG.debug('Added new charging session for vehicle %s to database', self.vehicle.vin)
351
- self.last_charging_session = new_session
352
- except DatabaseError as err:
353
- session.rollback()
354
- LOG.error('DatabaseError while adding charging session for vehicle %s to database: %s', self.vehicle.vin, err)
371
+ # when the incoming connected state was during the last session, this is a continuation
372
+ if self.last_charging_session is None or element.last_changed is not None and element.last_changed > \
373
+ (self.last_charging_session.session_end_date
374
+ or self.last_charging_session.plug_unlocked_date
375
+ or datetime.min.replace(tzinfo=timezone.utc)):
376
+ LOG.info("Starting new charging session for vehicle %s due to connector connected state on startup", self.vehicle.vin)
377
+ new_session: ChargingSession = ChargingSession(vin=self.vehicle.vin, plug_connected_date=element.last_changed)
378
+ try:
379
+ session.add(new_session)
380
+ self._update_session_odometer(session, new_session)
381
+ self._update_session_position(session, new_session)
382
+ session.commit()
383
+ LOG.debug('Added new charging session for vehicle %s to database', self.vehicle.vin)
384
+ self.last_charging_session = new_session
385
+ except DatabaseError as err:
386
+ session.rollback()
387
+ LOG.error('DatabaseError while adding charging session for vehicle %s to database: %s', self.vehicle.vin, err)
388
+ self.database_plugin.healthy._set_value(value=False) # pylint: disable=protected-access
355
389
  elif self.last_charging_session is not None and not self.last_charging_session.was_connected():
356
390
  try:
357
391
  self.last_charging_session.plug_connected_date = element.last_changed
@@ -360,12 +394,13 @@ class ChargingAgent(BaseAgent):
360
394
  except DatabaseError as err:
361
395
  session.rollback()
362
396
  LOG.error('DatabaseError while changing charging session for vehicle %s to database: %s', self.vehicle.vin, err)
397
+ self.database_plugin.healthy._set_value(value=False) # pylint: disable=protected-access
363
398
  self.carconnectivity_last_connector_state = element.value
364
399
  self.session_factory.remove()
365
400
 
366
401
  def __on_connector_lock_state_change(self, element: EnumAttribute[ChargingConnector.ChargingConnectorLockState], flags: Observable.ObserverEvent) -> None:
367
402
  del flags
368
- if self.vehicle.carconnectivity_vehicle is None:
403
+ if self.carconnectivity_vehicle is None:
369
404
  raise ValueError("Vehicle's carconnectivity_vehicle attribute is None")
370
405
 
371
406
  with self.session_factory() as session:
@@ -377,18 +412,33 @@ class ChargingAgent(BaseAgent):
377
412
  if element.value == ChargingConnector.ChargingConnectorLockState.LOCKED \
378
413
  and self.carconnectivity_last_connector_lock_state != ChargingConnector.ChargingConnectorLockState.LOCKED:
379
414
  if self.last_charging_session is None or self.last_charging_session.is_closed():
380
- LOG.info("Starting new charging session for vehicle %s due to connector locked state", self.vehicle.vin)
381
- new_session: ChargingSession = ChargingSession(vin=self.vehicle.vin, plug_locked_date=element.last_changed)
382
- try:
383
- session.add(new_session)
384
- self._update_session_odometer(session, new_session)
385
- self._update_session_position(session, new_session)
386
- session.commit()
387
- LOG.debug('Added new charging session for vehicle %s to database', self.vehicle.vin)
388
- self.last_charging_session = new_session
389
- except DatabaseError as err:
390
- session.rollback()
391
- LOG.error('DatabaseError while adding charging session for vehicle %s to database: %s', self.vehicle.vin, err)
415
+ # In case this was an interrupted charging session (interrupt no longer than 24hours), continue by erasing end time
416
+ if self.last_charging_session is not None and not self.last_charging_session.was_disconnected() \
417
+ and (self.last_charging_session.plug_unlocked_date is None
418
+ or self.last_charging_session.plug_unlocked_date > ((element.last_changed or datetime.now(timezone.utc))
419
+ - timedelta(hours=24))):
420
+ LOG.debug("found a closed charging session that was not disconneced. This could be an interrupted session we want to continue")
421
+ try:
422
+ self.last_charging_session.plug_unlocked_date = None
423
+ session.commit()
424
+ except DatabaseError as err:
425
+ session.rollback()
426
+ LOG.error('DatabaseError while changing charging session for vehicle %s to database: %s', self.vehicle.vin, err)
427
+ self.database_plugin.healthy._set_value(value=False) # pylint: disable=protected-access
428
+ else:
429
+ LOG.info("Starting new charging session for vehicle %s due to connector locked state", self.vehicle.vin)
430
+ new_session: ChargingSession = ChargingSession(vin=self.vehicle.vin, plug_locked_date=element.last_changed)
431
+ try:
432
+ session.add(new_session)
433
+ self._update_session_odometer(session, new_session)
434
+ self._update_session_position(session, new_session)
435
+ session.commit()
436
+ LOG.debug('Added new charging session for vehicle %s to database', self.vehicle.vin)
437
+ self.last_charging_session = new_session
438
+ except DatabaseError as err:
439
+ session.rollback()
440
+ LOG.error('DatabaseError while adding charging session for vehicle %s to database: %s', self.vehicle.vin, err)
441
+ self.database_plugin.healthy._set_value(value=False) # pylint: disable=protected-access
392
442
  elif not self.last_charging_session.was_locked():
393
443
  LOG.debug("Continuing existing charging session for vehicle %s, writing locked date", self.vehicle.vin)
394
444
  try:
@@ -399,6 +449,7 @@ class ChargingAgent(BaseAgent):
399
449
  except DatabaseError as err:
400
450
  session.rollback()
401
451
  LOG.error('DatabaseError while starting charging session for vehicle %s in database: %s', self.vehicle.vin, err)
452
+ self.database_plugin.healthy._set_value(value=False) # pylint: disable=protected-access
402
453
  elif element.value != ChargingConnector.ChargingConnectorLockState.LOCKED \
403
454
  and self.carconnectivity_last_connector_lock_state == ChargingConnector.ChargingConnectorLockState.LOCKED:
404
455
  if self.last_charging_session is not None and not self.last_charging_session.was_unlocked():
@@ -409,6 +460,7 @@ class ChargingAgent(BaseAgent):
409
460
  except DatabaseError as err:
410
461
  session.rollback()
411
462
  LOG.error('DatabaseError while ending charging session for vehicle %s in database: %s', self.vehicle.vin, err)
463
+ self.database_plugin.healthy._set_value(value=False) # pylint: disable=protected-access
412
464
  # Create charging session when locked at startup
413
465
  elif element.value == ChargingConnector.ChargingConnectorLockState.LOCKED \
414
466
  and self.carconnectivity_last_connector_lock_state == ChargingConnector.ChargingConnectorLockState.LOCKED:
@@ -425,6 +477,7 @@ class ChargingAgent(BaseAgent):
425
477
  except DatabaseError as err:
426
478
  session.rollback()
427
479
  LOG.error('DatabaseError while adding charging session for vehicle %s to database: %s', self.vehicle.vin, err)
480
+ self.database_plugin.healthy._set_value(value=False) # pylint: disable=protected-access
428
481
  elif self.last_charging_session is not None and not self.last_charging_session.was_locked():
429
482
  try:
430
483
  self.last_charging_session.plug_locked_date = element.last_changed
@@ -433,62 +486,110 @@ class ChargingAgent(BaseAgent):
433
486
  except DatabaseError as err:
434
487
  session.rollback()
435
488
  LOG.error('DatabaseError while changing charging session for vehicle %s to database: %s', self.vehicle.vin, err)
489
+ self.database_plugin.healthy._set_value(value=False) # pylint: disable=protected-access
436
490
  self.carconnectivity_last_connector_lock_state = element.value
437
491
  self.session_factory.remove()
438
492
 
439
493
  def _update_session_odometer(self, session: Session, charging_session: ChargingSession) -> None:
440
- if self.vehicle.carconnectivity_vehicle is None:
494
+ if self.carconnectivity_vehicle is None:
441
495
  raise ValueError("Vehicle's carconnectivity_vehicle attribute is None")
442
- if self.vehicle.carconnectivity_vehicle.odometer.enabled:
496
+ if self.carconnectivity_vehicle.odometer.enabled:
443
497
  if charging_session.session_odometer is None:
444
498
  try:
445
- charging_session.session_odometer = self.vehicle.carconnectivity_vehicle.odometer.value
499
+ charging_session.session_odometer = self.carconnectivity_vehicle.odometer.value
446
500
  except DatabaseError as err:
447
501
  session.rollback()
448
502
  LOG.error('DatabaseError while updating odometer for charging session of vehicle %s in database: %s', self.vehicle.vin, err)
503
+ self.database_plugin.healthy._set_value(value=False) # pylint: disable=protected-access
449
504
 
450
505
  def _update_session_charging_type(self, session: Session, charging_session: ChargingSession) -> None:
451
- if self.vehicle.carconnectivity_vehicle is None:
506
+ if self.carconnectivity_vehicle is None:
452
507
  raise ValueError("Vehicle's carconnectivity_vehicle attribute is None")
453
- if isinstance(self.vehicle.carconnectivity_vehicle, ElectricVehicle) and self.vehicle.carconnectivity_vehicle.charging.type.enabled \
454
- and self.vehicle.carconnectivity_vehicle.charging.type.value is not None:
508
+ if isinstance(self.carconnectivity_vehicle, ElectricVehicle) and self.carconnectivity_vehicle.charging.type.enabled \
509
+ and self.carconnectivity_vehicle.charging.type.value is not None:
455
510
  if charging_session.charging_type is None:
456
511
  try:
457
- charging_session.charging_type = self.vehicle.carconnectivity_vehicle.charging.type.value
512
+ charging_session.charging_type = self.carconnectivity_vehicle.charging.type.value
458
513
  except DatabaseError as err:
459
514
  session.rollback()
460
515
  LOG.error('DatabaseError while updating charging type for charging session of vehicle %s in database: %s', self.vehicle.vin, err)
516
+ self.database_plugin.healthy._set_value(value=False) # pylint: disable=protected-access
461
517
 
462
518
  def _update_session_position(self, session: Session, charging_session: ChargingSession) -> None:
463
- if self.vehicle.carconnectivity_vehicle is None:
519
+ if self.carconnectivity_vehicle is None:
464
520
  raise ValueError("Vehicle's carconnectivity_vehicle attribute is None")
465
- if self.vehicle.carconnectivity_vehicle.position.enabled and self.vehicle.carconnectivity_vehicle.position.latitude.enabled \
466
- and self.vehicle.carconnectivity_vehicle.position.longitude.enabled \
467
- and self.vehicle.carconnectivity_vehicle.position.latitude.value is not None \
468
- and self.vehicle.carconnectivity_vehicle.position.longitude.value is not None:
521
+ if self.carconnectivity_vehicle.position.enabled and self.carconnectivity_vehicle.position.latitude.enabled \
522
+ and self.carconnectivity_vehicle.position.longitude.enabled \
523
+ and self.carconnectivity_vehicle.position.latitude.value is not None \
524
+ and self.carconnectivity_vehicle.position.longitude.value is not None:
469
525
  if charging_session.session_position_latitude is None and charging_session.session_position_longitude is None:
470
526
  try:
471
- charging_session.session_position_latitude = self.vehicle.carconnectivity_vehicle.position.latitude.value
472
- charging_session.session_position_longitude = self.vehicle.carconnectivity_vehicle.position.longitude.value
527
+ charging_session.session_position_latitude = self.carconnectivity_vehicle.position.latitude.value
528
+ charging_session.session_position_longitude = self.carconnectivity_vehicle.position.longitude.value
473
529
  except DatabaseError as err:
474
530
  session.rollback()
475
531
  LOG.error('DatabaseError while updating position for charging session of vehicle %s in database: %s', self.vehicle.vin, err)
476
- if charging_session.location is None and self.vehicle.carconnectivity_vehicle.position.location.enabled:
477
- location: Location = Location.from_carconnectivity_location(location=self.vehicle.carconnectivity_vehicle.position.location)
532
+ self.database_plugin.healthy._set_value(value=False) # pylint: disable=protected-access
533
+ if charging_session.location is None and self.carconnectivity_vehicle.position.location.enabled:
534
+ location: Location = Location.from_carconnectivity_location(location=self.carconnectivity_vehicle.position.location)
478
535
  try:
479
536
  location = session.merge(location)
480
537
  charging_session.location = location
481
538
  except DatabaseError as err:
482
539
  session.rollback()
483
540
  LOG.error('DatabaseError while merging location for charging session of vehicle %s in database: %s', self.vehicle.vin, err)
541
+ self.database_plugin.healthy._set_value(value=False) # pylint: disable=protected-access
484
542
  if charging_session.charging_station is None \
485
- and isinstance(self.vehicle.carconnectivity_vehicle, ElectricVehicle) and self.vehicle.carconnectivity_vehicle.charging is not None \
486
- and self.vehicle.carconnectivity_vehicle.charging.enabled and self.vehicle.carconnectivity_vehicle.charging.charging_station.enabled:
543
+ and isinstance(self.carconnectivity_vehicle, ElectricVehicle) and self.carconnectivity_vehicle.charging is not None \
544
+ and self.carconnectivity_vehicle.charging.enabled and self.carconnectivity_vehicle.charging.charging_station.enabled:
487
545
  charging_station: ChargingStation = ChargingStation.from_carconnectivity_charging_station(
488
- charging_station=self.vehicle.carconnectivity_vehicle.charging.charging_station)
546
+ charging_station=self.carconnectivity_vehicle.charging.charging_station)
489
547
  try:
490
548
  charging_station = session.merge(charging_station)
491
549
  charging_session.charging_station = charging_station
492
550
  except DatabaseError as err:
493
551
  session.rollback()
494
552
  LOG.error('DatabaseError while merging charging station for charging session of vehicle %s in database: %s', self.vehicle.vin, err)
553
+ self.database_plugin.healthy._set_value(value=False) # pylint: disable=protected-access
554
+
555
+ def _on_charging_type_change(self, element: EnumAttribute[Charging.ChargingType], flags: Observable.ObserverEvent) -> None:
556
+ del flags
557
+ if element.enabled:
558
+ with self.session_factory() as session:
559
+ with self.last_charging_session_lock:
560
+ if self.last_charging_session is not None:
561
+ self.last_charging_session = session.merge(self.last_charging_session)
562
+ session.refresh(self.last_charging_session)
563
+ if self.last_charging_session is not None and not self.last_charging_session.is_closed() \
564
+ and element.value in [Charging.ChargingType.AC, Charging.ChargingType.DC]:
565
+ try:
566
+ self.last_charging_session.charging_type = element.value
567
+ session.commit()
568
+ except DatabaseError as err:
569
+ session.rollback()
570
+ LOG.error('DatabaseError while updating type of charging session for vehicle %s in database: %s', self.vehicle.vin, err)
571
+ self.database_plugin.healthy._set_value(value=False) # pylint: disable=protected-access
572
+ self.session_factory.remove()
573
+
574
+ def _on_battery_level_change(self, element: FloatAttribute, flags: Observable.ObserverEvent) -> None:
575
+ del flags
576
+ if element.enabled and element.value is not None:
577
+ # We try to see if there was a late battery level update for a finished session
578
+ with self.session_factory() as session:
579
+ with self.last_charging_session_lock:
580
+ if self.last_charging_session is not None:
581
+ self.last_charging_session = session.merge(self.last_charging_session)
582
+ session.refresh(self.last_charging_session)
583
+ if self.last_charging_session is not None and self.last_charging_session.session_end_date is not None:
584
+ if element.last_updated is not None and (element.last_updated <= (self.last_charging_session.session_end_date + timedelta(minutes=1))):
585
+ # Only update if we have no end level yet or the new level is higher than the previous one (this happens with late level updates)
586
+ if self.last_charging_session.end_level is None or self.last_charging_session.end_level < element.value:
587
+ try:
588
+ self.last_charging_session.end_level = element.value
589
+ session.commit()
590
+ except DatabaseError as err:
591
+ session.rollback()
592
+ LOG.error('DatabaseError while updating battery level of charging session for vehicle %s in database: %s',
593
+ self.vehicle.vin, err)
594
+ self.database_plugin.healthy._set_value(value=False) # pylint: disable=protected-access
595
+ self.session_factory.remove()