carconnectivity-plugin-database 0.1a11__tar.gz → 0.1a14__tar.gz

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 (64) hide show
  1. {carconnectivity_plugin_database-0.1a11/src/carconnectivity_plugin_database.egg-info → carconnectivity_plugin_database-0.1a14}/PKG-INFO +2 -2
  2. {carconnectivity_plugin_database-0.1a11 → carconnectivity_plugin_database-0.1a14}/pyproject.toml +1 -1
  3. {carconnectivity_plugin_database-0.1a11 → carconnectivity_plugin_database-0.1a14/src/carconnectivity_plugin_database.egg-info}/PKG-INFO +2 -2
  4. {carconnectivity_plugin_database-0.1a11 → carconnectivity_plugin_database-0.1a14}/src/carconnectivity_plugin_database.egg-info/SOURCES.txt +3 -0
  5. {carconnectivity_plugin_database-0.1a11 → carconnectivity_plugin_database-0.1a14}/src/carconnectivity_plugin_database.egg-info/requires.txt +1 -1
  6. {carconnectivity_plugin_database-0.1a11 → carconnectivity_plugin_database-0.1a14}/src/carconnectivity_plugins/database/_version.py +3 -3
  7. {carconnectivity_plugin_database-0.1a11 → carconnectivity_plugin_database-0.1a14}/src/carconnectivity_plugins/database/agents/charging_agent.py +132 -24
  8. {carconnectivity_plugin_database-0.1a11 → carconnectivity_plugin_database-0.1a14}/src/carconnectivity_plugins/database/agents/drive_state_agent.py +36 -2
  9. {carconnectivity_plugin_database-0.1a11 → carconnectivity_plugin_database-0.1a14}/src/carconnectivity_plugins/database/agents/trip_agent.py +1 -1
  10. {carconnectivity_plugin_database-0.1a11 → carconnectivity_plugin_database-0.1a14}/src/carconnectivity_plugins/database/model/__init__.py +4 -1
  11. {carconnectivity_plugin_database-0.1a11 → carconnectivity_plugin_database-0.1a14}/src/carconnectivity_plugins/database/model/charging_power.py +7 -2
  12. {carconnectivity_plugin_database-0.1a11 → carconnectivity_plugin_database-0.1a14}/src/carconnectivity_plugins/database/model/charging_rate.py +7 -2
  13. {carconnectivity_plugin_database-0.1a11 → carconnectivity_plugin_database-0.1a14}/src/carconnectivity_plugins/database/model/charging_session.py +18 -2
  14. {carconnectivity_plugin_database-0.1a11 → carconnectivity_plugin_database-0.1a14}/src/carconnectivity_plugins/database/model/charging_state.py +6 -2
  15. carconnectivity_plugin_database-0.1a14/src/carconnectivity_plugins/database/model/charging_station.py +66 -0
  16. {carconnectivity_plugin_database-0.1a11 → carconnectivity_plugin_database-0.1a14}/src/carconnectivity_plugins/database/model/climatization_state.py +6 -2
  17. {carconnectivity_plugin_database-0.1a11 → carconnectivity_plugin_database-0.1a14}/src/carconnectivity_plugins/database/model/connection_state.py +6 -2
  18. {carconnectivity_plugin_database-0.1a11 → carconnectivity_plugin_database-0.1a14}/src/carconnectivity_plugins/database/model/drive.py +7 -0
  19. {carconnectivity_plugin_database-0.1a11 → carconnectivity_plugin_database-0.1a14}/src/carconnectivity_plugins/database/model/drive_level.py +6 -2
  20. {carconnectivity_plugin_database-0.1a11 → carconnectivity_plugin_database-0.1a14}/src/carconnectivity_plugins/database/model/drive_range.py +6 -2
  21. carconnectivity_plugin_database-0.1a14/src/carconnectivity_plugins/database/model/drive_range_full.py +53 -0
  22. carconnectivity_plugin_database-0.1a14/src/carconnectivity_plugins/database/model/location.py +85 -0
  23. {carconnectivity_plugin_database-0.1a11 → carconnectivity_plugin_database-0.1a14}/src/carconnectivity_plugins/database/model/outside_temperature.py +6 -2
  24. {carconnectivity_plugin_database-0.1a11 → carconnectivity_plugin_database-0.1a14}/src/carconnectivity_plugins/database/model/state.py +6 -2
  25. {carconnectivity_plugin_database-0.1a11 → carconnectivity_plugin_database-0.1a14}/src/carconnectivity_plugins/database/model/trip.py +13 -4
  26. {carconnectivity_plugin_database-0.1a11 → carconnectivity_plugin_database-0.1a14}/src/carconnectivity_plugins/database/model/vehicle.py +7 -1
  27. {carconnectivity_plugin_database-0.1a11 → carconnectivity_plugin_database-0.1a14}/.flake8 +0 -0
  28. {carconnectivity_plugin_database-0.1a11 → carconnectivity_plugin_database-0.1a14}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  29. {carconnectivity_plugin_database-0.1a11 → carconnectivity_plugin_database-0.1a14}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  30. {carconnectivity_plugin_database-0.1a11 → carconnectivity_plugin_database-0.1a14}/.github/dependabot.yml +0 -0
  31. {carconnectivity_plugin_database-0.1a11 → carconnectivity_plugin_database-0.1a14}/.github/workflows/build.yml +0 -0
  32. {carconnectivity_plugin_database-0.1a11 → carconnectivity_plugin_database-0.1a14}/.github/workflows/build_and_publish.yml +0 -0
  33. {carconnectivity_plugin_database-0.1a11 → carconnectivity_plugin_database-0.1a14}/.github/workflows/codeql-analysis.yml +0 -0
  34. {carconnectivity_plugin_database-0.1a11 → carconnectivity_plugin_database-0.1a14}/.gitignore +0 -0
  35. {carconnectivity_plugin_database-0.1a11 → carconnectivity_plugin_database-0.1a14}/LICENSE +0 -0
  36. {carconnectivity_plugin_database-0.1a11 → carconnectivity_plugin_database-0.1a14}/Makefile +0 -0
  37. {carconnectivity_plugin_database-0.1a11 → carconnectivity_plugin_database-0.1a14}/README.md +0 -0
  38. {carconnectivity_plugin_database-0.1a11 → carconnectivity_plugin_database-0.1a14}/doc/Config.md +0 -0
  39. {carconnectivity_plugin_database-0.1a11 → carconnectivity_plugin_database-0.1a14}/setup.cfg +0 -0
  40. {carconnectivity_plugin_database-0.1a11 → carconnectivity_plugin_database-0.1a14}/setup_requirements.txt +0 -0
  41. {carconnectivity_plugin_database-0.1a11 → carconnectivity_plugin_database-0.1a14}/src/carconnectivity_database/__init__.py +0 -0
  42. {carconnectivity_plugin_database-0.1a11 → carconnectivity_plugin_database-0.1a14}/src/carconnectivity_database/carconnectivity_database_base.py +0 -0
  43. {carconnectivity_plugin_database-0.1a11 → carconnectivity_plugin_database-0.1a14}/src/carconnectivity_plugin_database.egg-info/dependency_links.txt +0 -0
  44. {carconnectivity_plugin_database-0.1a11 → carconnectivity_plugin_database-0.1a14}/src/carconnectivity_plugin_database.egg-info/entry_points.txt +0 -0
  45. {carconnectivity_plugin_database-0.1a11 → carconnectivity_plugin_database-0.1a14}/src/carconnectivity_plugin_database.egg-info/top_level.txt +0 -0
  46. {carconnectivity_plugin_database-0.1a11 → carconnectivity_plugin_database-0.1a14}/src/carconnectivity_plugins/database/__init__.py +0 -0
  47. {carconnectivity_plugin_database-0.1a11 → carconnectivity_plugin_database-0.1a14}/src/carconnectivity_plugins/database/agents/base_agent.py +0 -0
  48. {carconnectivity_plugin_database-0.1a11 → carconnectivity_plugin_database-0.1a14}/src/carconnectivity_plugins/database/agents/climatization_agent.py +0 -0
  49. {carconnectivity_plugin_database-0.1a11 → carconnectivity_plugin_database-0.1a14}/src/carconnectivity_plugins/database/agents/state_agent.py +0 -0
  50. {carconnectivity_plugin_database-0.1a11 → carconnectivity_plugin_database-0.1a14}/src/carconnectivity_plugins/database/model/alembic.ini +0 -0
  51. {carconnectivity_plugin_database-0.1a11 → carconnectivity_plugin_database-0.1a14}/src/carconnectivity_plugins/database/model/base.py +0 -0
  52. {carconnectivity_plugin_database-0.1a11 → carconnectivity_plugin_database-0.1a14}/src/carconnectivity_plugins/database/model/carconnectivity_schema/README +0 -0
  53. {carconnectivity_plugin_database-0.1a11 → carconnectivity_plugin_database-0.1a14}/src/carconnectivity_plugins/database/model/carconnectivity_schema/__init__.py +0 -0
  54. {carconnectivity_plugin_database-0.1a11 → carconnectivity_plugin_database-0.1a14}/src/carconnectivity_plugins/database/model/carconnectivity_schema/env.py +0 -0
  55. {carconnectivity_plugin_database-0.1a11 → carconnectivity_plugin_database-0.1a14}/src/carconnectivity_plugins/database/model/carconnectivity_schema/script.py.mako +0 -0
  56. {carconnectivity_plugin_database-0.1a11 → carconnectivity_plugin_database-0.1a14}/src/carconnectivity_plugins/database/model/carconnectivity_schema/versions/__init__.py +0 -0
  57. {carconnectivity_plugin_database-0.1a11 → carconnectivity_plugin_database-0.1a14}/src/carconnectivity_plugins/database/model/datetime_decorator.py +0 -0
  58. {carconnectivity_plugin_database-0.1a11 → carconnectivity_plugin_database-0.1a14}/src/carconnectivity_plugins/database/model/migrations.py +0 -0
  59. {carconnectivity_plugin_database-0.1a11 → carconnectivity_plugin_database-0.1a14}/src/carconnectivity_plugins/database/model/tag.py +0 -0
  60. {carconnectivity_plugin_database-0.1a11 → carconnectivity_plugin_database-0.1a14}/src/carconnectivity_plugins/database/model/timedelta_decorator.py +0 -0
  61. {carconnectivity_plugin_database-0.1a11 → carconnectivity_plugin_database-0.1a14}/src/carconnectivity_plugins/database/plugin.py +0 -0
  62. {carconnectivity_plugin_database-0.1a11 → carconnectivity_plugin_database-0.1a14}/src/carconnectivity_plugins/database/ui/plugin_ui.py +0 -0
  63. {carconnectivity_plugin_database-0.1a11 → carconnectivity_plugin_database-0.1a14}/src/carconnectivity_plugins/database/ui/templates/database/status.html +0 -0
  64. {carconnectivity_plugin_database-0.1a11 → carconnectivity_plugin_database-0.1a14}/test/integration_test/carConnectivity.json +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: carconnectivity-plugin-database
3
- Version: 0.1a11
3
+ Version: 0.1a14
4
4
  Summary: CarConnectivity plugin for storing data to Databases
5
5
  Author: Till Steinbach
6
6
  License-Expression: MIT
@@ -19,7 +19,7 @@ Classifier: Topic :: Home Automation
19
19
  Requires-Python: >=3.9
20
20
  Description-Content-Type: text/markdown
21
21
  License-File: LICENSE
22
- Requires-Dist: carconnectivity>=0.9.2
22
+ Requires-Dist: carconnectivity>=0.10
23
23
  Requires-Dist: sqlalchemy~=2.0.45
24
24
  Requires-Dist: psycopg2-binary~=2.9.11
25
25
  Requires-Dist: alembic~=1.17.2
@@ -14,7 +14,7 @@ authors = [
14
14
  { name = "Till Steinbach" }
15
15
  ]
16
16
  dependencies = [
17
- "carconnectivity>=0.9.2",
17
+ "carconnectivity>=0.10",
18
18
  "sqlalchemy~=2.0.45",
19
19
  "psycopg2-binary~=2.9.11",
20
20
  "alembic~=1.17.2",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: carconnectivity-plugin-database
3
- Version: 0.1a11
3
+ Version: 0.1a14
4
4
  Summary: CarConnectivity plugin for storing data to Databases
5
5
  Author: Till Steinbach
6
6
  License-Expression: MIT
@@ -19,7 +19,7 @@ Classifier: Topic :: Home Automation
19
19
  Requires-Python: >=3.9
20
20
  Description-Content-Type: text/markdown
21
21
  License-File: LICENSE
22
- Requires-Dist: carconnectivity>=0.9.2
22
+ Requires-Dist: carconnectivity>=0.10
23
23
  Requires-Dist: sqlalchemy~=2.0.45
24
24
  Requires-Dist: psycopg2-binary~=2.9.11
25
25
  Requires-Dist: alembic~=1.17.2
@@ -36,12 +36,15 @@ src/carconnectivity_plugins/database/model/charging_power.py
36
36
  src/carconnectivity_plugins/database/model/charging_rate.py
37
37
  src/carconnectivity_plugins/database/model/charging_session.py
38
38
  src/carconnectivity_plugins/database/model/charging_state.py
39
+ src/carconnectivity_plugins/database/model/charging_station.py
39
40
  src/carconnectivity_plugins/database/model/climatization_state.py
40
41
  src/carconnectivity_plugins/database/model/connection_state.py
41
42
  src/carconnectivity_plugins/database/model/datetime_decorator.py
42
43
  src/carconnectivity_plugins/database/model/drive.py
43
44
  src/carconnectivity_plugins/database/model/drive_level.py
44
45
  src/carconnectivity_plugins/database/model/drive_range.py
46
+ src/carconnectivity_plugins/database/model/drive_range_full.py
47
+ src/carconnectivity_plugins/database/model/location.py
45
48
  src/carconnectivity_plugins/database/model/migrations.py
46
49
  src/carconnectivity_plugins/database/model/outside_temperature.py
47
50
  src/carconnectivity_plugins/database/model/state.py
@@ -1,4 +1,4 @@
1
- carconnectivity>=0.9.2
1
+ carconnectivity>=0.10
2
2
  sqlalchemy~=2.0.45
3
3
  psycopg2-binary~=2.9.11
4
4
  alembic~=1.17.2
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '0.1a11'
32
- __version_tuple__ = version_tuple = (0, 1, 'a11')
31
+ __version__ = version = '0.1a14'
32
+ __version_tuple__ = version_tuple = (0, 1, 'a14')
33
33
 
34
- __commit_id__ = commit_id = 'gf46ffbb8b'
34
+ __commit_id__ = commit_id = 'g0e0346a6f'
@@ -2,8 +2,8 @@ from __future__ import annotations
2
2
  from typing import TYPE_CHECKING
3
3
 
4
4
  import logging
5
+ from datetime import timedelta
5
6
 
6
- from sqlalchemy import and_
7
7
  from sqlalchemy.exc import DatabaseError
8
8
 
9
9
  from carconnectivity.observable import Observable
@@ -17,6 +17,8 @@ from carconnectivity_plugins.database.model.charging_state import ChargingState
17
17
  from carconnectivity_plugins.database.model.charging_rate import ChargingRate
18
18
  from carconnectivity_plugins.database.model.charging_power import ChargingPower
19
19
  from carconnectivity_plugins.database.model.charging_session import ChargingSession
20
+ from carconnectivity_plugins.database.model.location import Location
21
+ from carconnectivity_plugins.database.model.charging_station import ChargingStation
20
22
 
21
23
  if TYPE_CHECKING:
22
24
  from typing import Optional
@@ -40,8 +42,7 @@ class ChargingAgent(BaseAgent):
40
42
  self.session: Session = session
41
43
  self.vehicle: Vehicle = vehicle
42
44
 
43
- self.last_charging_session: Optional[ChargingSession] = session.query(ChargingSession).filter(and_(ChargingSession.vehicle == vehicle,
44
- ChargingSession.session_start_date.is_not(None))) \
45
+ self.last_charging_session: Optional[ChargingSession] = session.query(ChargingSession).filter(ChargingSession.vehicle == vehicle) \
45
46
  .order_by(ChargingSession.session_start_date.desc()).first()
46
47
  self.carconnectivity_last_charging_state: Optional[Charging.ChargingState] = vehicle.carconnectivity_vehicle.charging.state.value
47
48
  self.carconnectivity_last_connector_state: Optional[ChargingConnector.ChargingConnectorConnectionState] = vehicle.carconnectivity_vehicle.charging\
@@ -49,7 +50,12 @@ class ChargingAgent(BaseAgent):
49
50
  self.carconnectivity_last_connector_lock_state: Optional[ChargingConnector.ChargingConnectorLockState] = vehicle.carconnectivity_vehicle.charging\
50
51
  .connector.lock_state.value
51
52
  if self.last_charging_session is not None and not self.last_charging_session.is_closed():
52
- if vehicle.carconnectivity_vehicle.charging.state.value in (Charging.ChargingState.CHARGING, Charging.ChargingState.CONSERVATION):
53
+ if vehicle.carconnectivity_vehicle.charging.state.value in (Charging.ChargingState.CHARGING, Charging.ChargingState.CONSERVATION) \
54
+ or (vehicle.carconnectivity_vehicle.charging.connector.connection_state.enabled
55
+ and vehicle.carconnectivity_vehicle.charging.connector.connection_state.value ==
56
+ ChargingConnector.ChargingConnectorConnectionState.CONNECTED) \
57
+ or (vehicle.carconnectivity_vehicle.charging.connector.lock_state.enabled
58
+ and vehicle.carconnectivity_vehicle.charging.connector.lock_state.value == ChargingConnector.ChargingConnectorLockState.LOCKED):
53
59
  LOG.info("Last charging session for vehicle %s is still open during startup, will continue this session", vehicle.vin)
54
60
  else:
55
61
  LOG.info("Last charging session for vehicle %s is still open during startup, but we are not charging, ignoring it", vehicle.vin)
@@ -118,24 +124,47 @@ class ChargingAgent(BaseAgent):
118
124
  if element.value in (Charging.ChargingState.CHARGING, Charging.ChargingState.CONSERVATION) \
119
125
  and self.carconnectivity_last_charging_state not in (Charging.ChargingState.CHARGING, Charging.ChargingState.CONSERVATION):
120
126
  if self.last_charging_session is None or self.last_charging_session.is_closed():
121
- LOG.info("Starting new charging session for vehicle %s", self.vehicle.vin)
122
- new_session: ChargingSession = ChargingSession(vin=self.vehicle.vin, session_start_date=element.last_updated)
123
- try:
124
- self.session.add(new_session)
125
- LOG.debug('Added new charging session for vehicle %s to database', self.vehicle.vin)
126
- self._update_session_odometer(new_session)
127
- self.last_charging_session = new_session
128
- except DatabaseError as err:
129
- self.session.rollback()
130
- LOG.error('DatabaseError while adding charging session for vehicle %s to database: %s', self.vehicle.vin, err)
127
+ # check that we are not resuming an old session
128
+ allowed_interrupt: timedelta = timedelta(hours=24)
129
+ # we allow longer CONSERVATION within the session
130
+ if element.value == Charging.ChargingState.CONSERVATION:
131
+ allowed_interrupt = timedelta(hours=300)
132
+ # We can reuse the session if the vehicle was connected and not disconnected in the meantime
133
+ # And the session end date was not set or is within the allowed interrupt time
134
+ if self.last_charging_session is not None \
135
+ and self.last_charging_session.was_connected() and not self.last_charging_session.was_disconnected() \
136
+ and (self.last_charging_session.session_end_date is None or element.last_changed is None
137
+ or self.last_charging_session.session_end_date > (element.last_changed - allowed_interrupt)):
138
+ LOG.debug("Continuing existing charging session for vehicle %s", self.vehicle.vin)
139
+ try:
140
+ self.last_charging_session.session_end_date = None
141
+ self.last_charging_session.end_level = None
142
+ except DatabaseError as err:
143
+ self.session.rollback()
144
+ LOG.error('DatabaseError while updating charging session for vehicle %s in database: %s', self.vehicle.vin, err)
145
+ else:
146
+ LOG.info("Starting new charging session for vehicle %s", self.vehicle.vin)
147
+ new_session: ChargingSession = ChargingSession(vin=self.vehicle.vin, session_start_date=element.last_changed)
148
+ try:
149
+ self.session.add(new_session)
150
+ LOG.debug('Added new charging session for vehicle %s to database', self.vehicle.vin)
151
+ self._update_session_odometer(new_session)
152
+ self._update_session_position(new_session)
153
+ self._update_session_charging_type(new_session)
154
+ self.last_charging_session = new_session
155
+ except DatabaseError as err:
156
+ self.session.rollback()
157
+ LOG.error('DatabaseError while adding charging session for vehicle %s to database: %s', self.vehicle.vin, err)
131
158
  else:
132
159
  if self.last_charging_session.was_started():
133
160
  LOG.debug("Continuing existing charging session for vehicle %s", self.vehicle.vin)
134
161
  else:
135
162
  LOG.debug("Starting charging in existing charging session for vehicle %s", self.vehicle.vin)
136
163
  try:
137
- self.last_charging_session.session_start_date = element.last_updated
164
+ self.last_charging_session.session_start_date = element.last_changed
138
165
  self._update_session_odometer(self.last_charging_session)
166
+ self._update_session_position(self.last_charging_session)
167
+ self._update_session_charging_type(self.last_charging_session)
139
168
  except DatabaseError as err:
140
169
  self.session.rollback()
141
170
  LOG.error('DatabaseError while starting charging session for vehicle %s in database: %s', self.vehicle.vin, err)
@@ -153,7 +182,7 @@ class ChargingAgent(BaseAgent):
153
182
  if self.last_charging_session is not None and not self.last_charging_session.was_ended():
154
183
  LOG.info("Ending charging session for vehicle %s", self.vehicle.vin)
155
184
  try:
156
- self.last_charging_session.session_end_date = element.last_updated
185
+ self.last_charging_session.session_end_date = element.last_changed
157
186
  except DatabaseError as err:
158
187
  self.session.rollback()
159
188
  LOG.error('DatabaseError while ending charging session for vehicle %s in database: %s', self.vehicle.vin, err)
@@ -230,11 +259,12 @@ class ChargingAgent(BaseAgent):
230
259
  if element.value == ChargingConnector.ChargingConnectorConnectionState.CONNECTED \
231
260
  and self.carconnectivity_last_connector_state != ChargingConnector.ChargingConnectorConnectionState.CONNECTED:
232
261
  if self.last_charging_session is None or self.last_charging_session.is_closed():
233
- LOG.info("Starting new charging session for vehicle %s", self.vehicle.vin)
234
- new_session: ChargingSession = ChargingSession(vin=self.vehicle.vin, plug_connected_date=element.last_updated)
262
+ LOG.info("Starting new charging session for vehicle %s due to connector connected state", self.vehicle.vin)
263
+ new_session: ChargingSession = ChargingSession(vin=self.vehicle.vin, plug_connected_date=element.last_changed)
235
264
  try:
236
265
  self.session.add(new_session)
237
266
  self._update_session_odometer(new_session)
267
+ self._update_session_position(new_session)
238
268
  LOG.debug('Added new charging session for vehicle %s to database', self.vehicle.vin)
239
269
  self.last_charging_session = new_session
240
270
  except DatabaseError as err:
@@ -243,8 +273,9 @@ class ChargingAgent(BaseAgent):
243
273
  elif not self.last_charging_session.was_connected():
244
274
  LOG.debug("Continuing existing charging session for vehicle %s, writing connected date", self.vehicle.vin)
245
275
  try:
246
- self.last_charging_session.plug_connected_date = element.last_updated
276
+ self.last_charging_session.plug_connected_date = element.last_changed
247
277
  self._update_session_odometer(self.last_charging_session)
278
+ self._update_session_position(self.last_charging_session)
248
279
  except DatabaseError as err:
249
280
  self.session.rollback()
250
281
  LOG.error('DatabaseError while starting charging session for vehicle %s in database: %s', self.vehicle.vin, err)
@@ -253,10 +284,32 @@ class ChargingAgent(BaseAgent):
253
284
  if self.last_charging_session is not None and not self.last_charging_session.was_disconnected():
254
285
  LOG.info("Writing plug disconnected date for charging session of vehicle %s", self.vehicle.vin)
255
286
  try:
256
- self.last_charging_session.plug_disconnected_date = element.last_updated
287
+ self.last_charging_session.plug_disconnected_date = element.last_changed
257
288
  except DatabaseError as err:
258
289
  self.session.rollback()
259
290
  LOG.error('DatabaseError while ending charging session for vehicle %s in database: %s', self.vehicle.vin, err)
291
+ # Create charging session when connected at startup
292
+ elif element.value == ChargingConnector.ChargingConnectorConnectionState.CONNECTED \
293
+ and self.carconnectivity_last_connector_state == ChargingConnector.ChargingConnectorConnectionState.CONNECTED:
294
+ if self.last_charging_session is None or self.last_charging_session.is_closed():
295
+ LOG.info("Starting new charging session for vehicle %s due to connector connected state", self.vehicle.vin)
296
+ new_session: ChargingSession = ChargingSession(vin=self.vehicle.vin, plug_connected_date=element.last_changed)
297
+ try:
298
+ self.session.add(new_session)
299
+ self._update_session_odometer(new_session)
300
+ self._update_session_position(new_session)
301
+ LOG.debug('Added new charging session for vehicle %s to database', self.vehicle.vin)
302
+ self.last_charging_session = new_session
303
+ except DatabaseError as err:
304
+ self.session.rollback()
305
+ LOG.error('DatabaseError while adding charging session for vehicle %s to database: %s', self.vehicle.vin, err)
306
+ elif self.last_charging_session is not None and not self.last_charging_session.was_connected():
307
+ try:
308
+ self.last_charging_session.plug_connected_date = element.last_changed
309
+ LOG.info("Writing plug connected date for charging session of vehicle %s", self.vehicle.vin)
310
+ except DatabaseError as err:
311
+ self.session.rollback()
312
+ LOG.error('DatabaseError while changing charging session for vehicle %s to database: %s', self.vehicle.vin, err)
260
313
  self.carconnectivity_last_connector_state = element.value
261
314
 
262
315
  def __on_connector_lock_state_change(self, element: EnumAttribute[ChargingConnector.ChargingConnectorLockState], flags: Observable.ObserverEvent) -> None:
@@ -270,11 +323,12 @@ class ChargingAgent(BaseAgent):
270
323
  if element.value == ChargingConnector.ChargingConnectorLockState.LOCKED \
271
324
  and self.carconnectivity_last_connector_lock_state != ChargingConnector.ChargingConnectorLockState.LOCKED:
272
325
  if self.last_charging_session is None or self.last_charging_session.is_closed():
273
- LOG.info("Starting new charging session for vehicle %s", self.vehicle.vin)
274
- new_session: ChargingSession = ChargingSession(vin=self.vehicle.vin, plug_locked_date=element.last_updated)
326
+ LOG.info("Starting new charging session for vehicle %s due to connector locked state", self.vehicle.vin)
327
+ new_session: ChargingSession = ChargingSession(vin=self.vehicle.vin, plug_locked_date=element.last_changed)
275
328
  try:
276
329
  self.session.add(new_session)
277
330
  self._update_session_odometer(new_session)
331
+ self._update_session_position(new_session)
278
332
  LOG.debug('Added new charging session for vehicle %s to database', self.vehicle.vin)
279
333
  self.last_charging_session = new_session
280
334
  except DatabaseError as err:
@@ -283,8 +337,9 @@ class ChargingAgent(BaseAgent):
283
337
  elif not self.last_charging_session.was_locked():
284
338
  LOG.debug("Continuing existing charging session for vehicle %s, writing locked date", self.vehicle.vin)
285
339
  try:
286
- self.last_charging_session.plug_locked_date = element.last_updated
340
+ self.last_charging_session.plug_locked_date = element.last_changed
287
341
  self._update_session_odometer(self.last_charging_session)
342
+ self._update_session_position(self.last_charging_session)
288
343
  except DatabaseError as err:
289
344
  self.session.rollback()
290
345
  LOG.error('DatabaseError while starting charging session for vehicle %s in database: %s', self.vehicle.vin, err)
@@ -293,10 +348,32 @@ class ChargingAgent(BaseAgent):
293
348
  if self.last_charging_session is not None and not self.last_charging_session.was_unlocked():
294
349
  LOG.info("Writing plug unlocked date for charging session of vehicle %s", self.vehicle.vin)
295
350
  try:
296
- self.last_charging_session.plug_unlocked_date = element.last_updated
351
+ self.last_charging_session.plug_unlocked_date = element.last_changed
297
352
  except DatabaseError as err:
298
353
  self.session.rollback()
299
354
  LOG.error('DatabaseError while ending charging session for vehicle %s in database: %s', self.vehicle.vin, err)
355
+ # Create charging session when locked at startup
356
+ elif element.value == ChargingConnector.ChargingConnectorLockState.LOCKED \
357
+ and self.carconnectivity_last_connector_lock_state == ChargingConnector.ChargingConnectorLockState.LOCKED:
358
+ if self.last_charging_session is None or self.last_charging_session.is_closed():
359
+ LOG.info("Starting new charging session for vehicle %s due to connector locked state", self.vehicle.vin)
360
+ new_session: ChargingSession = ChargingSession(vin=self.vehicle.vin, plug_locked_date=element.last_changed)
361
+ try:
362
+ self.session.add(new_session)
363
+ self._update_session_odometer(new_session)
364
+ self._update_session_position(new_session)
365
+ LOG.debug('Added new charging session for vehicle %s to database', self.vehicle.vin)
366
+ self.last_charging_session = new_session
367
+ except DatabaseError as err:
368
+ self.session.rollback()
369
+ LOG.error('DatabaseError while adding charging session for vehicle %s to database: %s', self.vehicle.vin, err)
370
+ elif self.last_charging_session is not None and not self.last_charging_session.was_locked():
371
+ try:
372
+ self.last_charging_session.plug_locked_date = element.last_changed
373
+ LOG.info("Writing plug locked date for charging session of vehicle %s", self.vehicle.vin)
374
+ except DatabaseError as err:
375
+ self.session.rollback()
376
+ LOG.error('DatabaseError while changing charging session for vehicle %s to database: %s', self.vehicle.vin, err)
300
377
  self.carconnectivity_last_connector_lock_state = element.value
301
378
 
302
379
  def _update_session_odometer(self, charging_session: ChargingSession) -> None:
@@ -310,6 +387,18 @@ class ChargingAgent(BaseAgent):
310
387
  self.session.rollback()
311
388
  LOG.error('DatabaseError while updating odometer for charging session of vehicle %s in database: %s', self.vehicle.vin, err)
312
389
 
390
+ def _update_session_charging_type(self, charging_session: ChargingSession) -> None:
391
+ if self.vehicle.carconnectivity_vehicle is None:
392
+ raise ValueError("Vehicle's carconnectivity_vehicle attribute is None")
393
+ if isinstance(self.vehicle.carconnectivity_vehicle, ElectricVehicle) and self.vehicle.carconnectivity_vehicle.charging.type.enabled \
394
+ and self.vehicle.carconnectivity_vehicle.charging.type.value is not None:
395
+ if charging_session.charging_type is None:
396
+ try:
397
+ charging_session.charging_type = self.vehicle.carconnectivity_vehicle.charging.type.value
398
+ except DatabaseError as err:
399
+ self.session.rollback()
400
+ LOG.error('DatabaseError while updating charging type for charging session of vehicle %s in database: %s', self.vehicle.vin, err)
401
+
313
402
  def _update_session_position(self, charging_session: ChargingSession) -> None:
314
403
  if self.vehicle.carconnectivity_vehicle is None:
315
404
  raise ValueError("Vehicle's carconnectivity_vehicle attribute is None")
@@ -324,3 +413,22 @@ class ChargingAgent(BaseAgent):
324
413
  except DatabaseError as err:
325
414
  self.session.rollback()
326
415
  LOG.error('DatabaseError while updating position for charging session of vehicle %s in database: %s', self.vehicle.vin, err)
416
+ if charging_session.location is None and self.vehicle.carconnectivity_vehicle.position.location.enabled:
417
+ location: Location = Location.from_carconnectivity_location(location=self.vehicle.carconnectivity_vehicle.position.location)
418
+ try:
419
+ location = self.session.merge(location)
420
+ charging_session.location = location
421
+ except DatabaseError as err:
422
+ self.session.rollback()
423
+ LOG.error('DatabaseError while merging location for charging session of vehicle %s in database: %s', self.vehicle.vin, err)
424
+ if charging_session.charging_station is None \
425
+ and isinstance(self.vehicle.carconnectivity_vehicle, ElectricVehicle) and self.vehicle.carconnectivity_vehicle.charging is not None \
426
+ and self.vehicle.carconnectivity_vehicle.charging.enabled and self.vehicle.carconnectivity_vehicle.charging.charging_station.enabled:
427
+ charging_station: ChargingStation = ChargingStation.from_carconnectivity_charging_station(
428
+ charging_station=self.vehicle.carconnectivity_vehicle.charging.charging_station)
429
+ try:
430
+ charging_station = self.session.merge(charging_station)
431
+ charging_session.charging_station = charging_station
432
+ except DatabaseError as err:
433
+ self.session.rollback()
434
+ LOG.error('DatabaseError while merging charging station for charging session of vehicle %s in database: %s', self.vehicle.vin, err)
@@ -10,6 +10,7 @@ from carconnectivity.observable import Observable
10
10
  from carconnectivity_plugins.database.agents.base_agent import BaseAgent
11
11
  from carconnectivity_plugins.database.model.drive_level import DriveLevel
12
12
  from carconnectivity_plugins.database.model.drive_range import DriveRange
13
+ from carconnectivity_plugins.database.model.drive_range_full import DriveRangeEstimatedFull
13
14
 
14
15
  if TYPE_CHECKING:
15
16
  from typing import Optional
@@ -22,6 +23,7 @@ if TYPE_CHECKING:
22
23
 
23
24
  LOG: logging.Logger = logging.getLogger("carconnectivity.plugins.database.agents.drive_state_agent")
24
25
 
26
+
25
27
  class DriveStateAgent(BaseAgent):
26
28
  def __init__(self, session: Session, drive: Drive) -> None:
27
29
  if drive is None or drive.carconnectivity_drive is None:
@@ -31,6 +33,8 @@ class DriveStateAgent(BaseAgent):
31
33
 
32
34
  self.last_level: Optional[DriveLevel] = session.query(DriveLevel).filter(DriveLevel.drive_id == drive.id).order_by(DriveLevel.first_date.desc()).first()
33
35
  self.last_range: Optional[DriveRange] = session.query(DriveRange).filter(DriveRange.drive_id == drive.id).order_by(DriveRange.first_date.desc()).first()
36
+ self.last_range_estimated_full: Optional[DriveRangeEstimatedFull] = session.query(DriveRangeEstimatedFull) \
37
+ .filter(DriveRangeEstimatedFull.drive_id == drive.id).order_by(DriveRangeEstimatedFull.first_date.desc()).first()
34
38
 
35
39
  drive.carconnectivity_drive.level.add_observer(self.__on_level_change, Observable.ObserverEvent.UPDATED)
36
40
  if drive.carconnectivity_drive.level.enabled:
@@ -40,6 +44,10 @@ class DriveStateAgent(BaseAgent):
40
44
  if drive.carconnectivity_drive.range.enabled:
41
45
  self.__on_range_change(drive.carconnectivity_drive.range, Observable.ObserverEvent.UPDATED)
42
46
 
47
+ drive.carconnectivity_drive.range_estimated_full.add_observer(self.__on_range_estimated_full_change, Observable.ObserverEvent.UPDATED)
48
+ if drive.carconnectivity_drive.range_estimated_full.enabled:
49
+ self.__on_range_estimated_full_change(drive.carconnectivity_drive.range_estimated_full, Observable.ObserverEvent.UPDATED)
50
+
43
51
  def __on_level_change(self, element: LevelAttribute, flags: Observable.ObserverEvent) -> None:
44
52
  del flags
45
53
  if element.enabled:
@@ -48,7 +56,7 @@ class DriveStateAgent(BaseAgent):
48
56
  if (self.last_level is None or self.last_level.level != element.value) \
49
57
  and element.last_updated is not None:
50
58
  new_level: DriveLevel = DriveLevel(drive_id=self.drive.id, first_date=element.last_updated, last_date=element.last_updated,
51
- level=element.value)
59
+ level=element.value)
52
60
  try:
53
61
  self.session.add(new_level)
54
62
  LOG.debug('Added new level %s for drive %s to database', element.value, self.drive.id)
@@ -74,7 +82,7 @@ class DriveStateAgent(BaseAgent):
74
82
  if (self.last_range is None or self.last_range.range != element.value) \
75
83
  and element.last_updated is not None:
76
84
  new_range: DriveRange = DriveRange(drive_id=self.drive.id, first_date=element.last_updated, last_date=element.last_updated,
77
- range=element.value)
85
+ range=element.value)
78
86
  try:
79
87
  self.session.add(new_range)
80
88
  LOG.debug('Added new range %s for drive %s to database', element.value, self.drive.id)
@@ -91,3 +99,29 @@ class DriveStateAgent(BaseAgent):
91
99
  except DatabaseError as err:
92
100
  self.session.rollback()
93
101
  LOG.error('DatabaseError while updating range for drive %s in database: %s', self.drive.id, err)
102
+
103
+ def __on_range_estimated_full_change(self, element: RangeAttribute, flags: Observable.ObserverEvent) -> None:
104
+ del flags
105
+ if element.enabled:
106
+ if self.last_range_estimated_full is not None:
107
+ self.session.refresh(self.last_range_estimated_full)
108
+ if (self.last_range_estimated_full is None or self.last_range_estimated_full.range_estimated_full != element.value) \
109
+ and element.last_updated is not None:
110
+ new_range: DriveRangeEstimatedFull = DriveRangeEstimatedFull(drive_id=self.drive.id, first_date=element.last_updated,
111
+ last_date=element.last_updated, range_estimated_full=element.value)
112
+ try:
113
+ self.session.add(new_range)
114
+ LOG.debug('Added new range_estimated_full %s for drive %s to database', element.value, self.drive.id)
115
+ self.last_range_estimated_full = new_range
116
+ except DatabaseError as err:
117
+ self.session.rollback()
118
+ LOG.error('DatabaseError while adding range_estimated_full for drive %s to database: %s', self.drive.id, err)
119
+ elif self.last_range_estimated_full is not None and self.last_range_estimated_full.range_estimated_full == element.value \
120
+ and element.last_updated is not None:
121
+ if self.last_range_estimated_full.last_date is None or element.last_updated > self.last_range_estimated_full.last_date:
122
+ try:
123
+ self.last_range_estimated_full.last_date = element.last_updated
124
+ LOG.debug('Updated range_estimated_full %s for drive %s in database', element.value, self.drive.id)
125
+ except DatabaseError as err:
126
+ self.session.rollback()
127
+ LOG.error('DatabaseError while updating range_estimated_full for drive %s in database: %s', self.drive.id, err)
@@ -90,7 +90,7 @@ class TripAgent(BaseAgent):
90
90
  if self.trip is not None:
91
91
  LOG.info("Ending trip for vehicle %s", self.vehicle.vin)
92
92
  try:
93
- self.trip.end_date = element.last_updated if element.last_updated is not None else datetime.now(tz=timezone.utc)
93
+ self.trip.destination_date = element.last_updated if element.last_updated is not None else datetime.now(tz=timezone.utc)
94
94
  if self.vehicle.carconnectivity_vehicle.odometer.enabled and \
95
95
  self.vehicle.carconnectivity_vehicle.odometer.value is not None:
96
96
  self.trip.destination_odometer = self.vehicle.carconnectivity_vehicle.odometer.value
@@ -4,11 +4,14 @@ from .charging_state import ChargingState # noqa: F401
4
4
  from .charging_rate import ChargingRate # noqa: F401
5
5
  from .charging_power import ChargingPower # noqa: F401
6
6
  from .charging_session import ChargingSession # noqa: F401
7
+ from .charging_station import ChargingStation # noqa: F401
7
8
  from .climatization_state import ClimatizationState # noqa: F401
9
+ from .connection_state import ConnectionState # noqa: F401
8
10
  from .drive import Drive # noqa: F401
9
11
  from .drive_level import DriveLevel # noqa: F401
10
12
  from .drive_range import DriveRange # noqa: F401
11
- from .connection_state import ConnectionState # noqa: F401
13
+ from .drive_range_full import DriveRangeEstimatedFull # noqa: F401
14
+ from .location import Location # noqa: F401
12
15
  from .outside_temperature import OutsideTemperature # noqa: F401
13
16
  from .state import State # noqa: F401
14
17
  from .tag import Tag # noqa: F401
@@ -1,10 +1,10 @@
1
1
  """ This module contains the Vehicle charging power database model"""
2
2
  from __future__ import annotations
3
- from typing import Optional
3
+ from typing import TYPE_CHECKING, Optional
4
4
 
5
5
  from datetime import datetime
6
6
 
7
- from sqlalchemy import ForeignKey
7
+ from sqlalchemy import ForeignKey, UniqueConstraint
8
8
  from sqlalchemy.orm import Mapped, mapped_column, relationship
9
9
 
10
10
  from sqlalchemy_utc import UtcDateTime
@@ -12,6 +12,10 @@ from sqlalchemy_utc import UtcDateTime
12
12
  from carconnectivity_plugins.database.model.base import Base
13
13
 
14
14
 
15
+ if TYPE_CHECKING:
16
+ from sqlalchemy import Constraint
17
+
18
+
15
19
  class ChargingPower(Base): # pylint: disable=too-few-public-methods
16
20
  """
17
21
  SQLAlchemy model representing a vehicle's charging power information over a time period.
@@ -32,6 +36,7 @@ class ChargingPower(Base): # pylint: disable=too-few-public-methods
32
36
  """
33
37
 
34
38
  __tablename__: str = 'charging_powers'
39
+ __table_args__: tuple[Constraint] = (UniqueConstraint("vin", "first_date", name="vin_first_date"),)
35
40
 
36
41
  id: Mapped[int] = mapped_column(primary_key=True)
37
42
  vin: Mapped[str] = mapped_column(ForeignKey("vehicles.vin"))
@@ -1,10 +1,10 @@
1
1
  """ This module contains the Vehicle charging rates database model"""
2
2
  from __future__ import annotations
3
- from typing import Optional
3
+ from typing import TYPE_CHECKING, Optional
4
4
 
5
5
  from datetime import datetime
6
6
 
7
- from sqlalchemy import ForeignKey
7
+ from sqlalchemy import ForeignKey, UniqueConstraint
8
8
  from sqlalchemy.orm import Mapped, mapped_column, relationship
9
9
 
10
10
  from sqlalchemy_utc import UtcDateTime
@@ -12,6 +12,10 @@ from sqlalchemy_utc import UtcDateTime
12
12
  from carconnectivity_plugins.database.model.base import Base
13
13
 
14
14
 
15
+ if TYPE_CHECKING:
16
+ from sqlalchemy import Constraint
17
+
18
+
15
19
  class ChargingRate(Base): # pylint: disable=too-few-public-methods
16
20
  """
17
21
  SQLAlchemy model representing a vehicle's charging type information over a time period.
@@ -32,6 +36,7 @@ class ChargingRate(Base): # pylint: disable=too-few-public-methods
32
36
  """
33
37
 
34
38
  __tablename__: str = 'charging_rates'
39
+ __table_args__: tuple[Constraint] = (UniqueConstraint("vin", "first_date", name="vin_first_date"),)
35
40
 
36
41
  id: Mapped[int] = mapped_column(primary_key=True)
37
42
  vin: Mapped[str] = mapped_column(ForeignKey("vehicles.vin"))
@@ -1,10 +1,10 @@
1
1
  """ This module contains the Vehicle charging sessions database model"""
2
2
  from __future__ import annotations
3
- from typing import Optional
3
+ from typing import TYPE_CHECKING, Optional
4
4
 
5
5
  from datetime import datetime
6
6
 
7
- from sqlalchemy import ForeignKey, Table, Column
7
+ from sqlalchemy import ForeignKey, Table, Column, UniqueConstraint
8
8
  from sqlalchemy.orm import Mapped, mapped_column, relationship, backref
9
9
 
10
10
  from sqlalchemy_utc import UtcDateTime
@@ -14,6 +14,10 @@ from carconnectivity.charging import Charging
14
14
  from carconnectivity_plugins.database.model.base import Base
15
15
 
16
16
 
17
+ if TYPE_CHECKING:
18
+ from sqlalchemy import Constraint
19
+
20
+
17
21
  charging_tag_association_table = Table('charging_sessions_tags', Base.metadata,
18
22
  Column('charging_sessions_id', ForeignKey('charging_sessions.id')),
19
23
  Column('tags_name', ForeignKey('tags.name'))
@@ -51,6 +55,7 @@ class ChargingSession(Base): # pylint: disable=too-few-public-methods
51
55
  """
52
56
 
53
57
  __tablename__: str = 'charging_sessions'
58
+ __table_args__: tuple[Constraint] = (UniqueConstraint("vin", "session_start_date", name="vin_session_start_date"),)
54
59
 
55
60
  id: Mapped[int] = mapped_column(primary_key=True)
56
61
  vin: Mapped[str] = mapped_column(ForeignKey("vehicles.vin"))
@@ -68,6 +73,17 @@ class ChargingSession(Base): # pylint: disable=too-few-public-methods
68
73
  session_position_longitude: Mapped[Optional[float]]
69
74
  session_odometer: Mapped[Optional[float]]
70
75
  charging_type: Mapped[Optional[Charging.ChargingType]]
76
+ location_uid: Mapped[Optional[str]] = mapped_column(ForeignKey("locations.uid"))
77
+ location: Mapped[Optional["Location"]] = relationship("Location")
78
+ charging_station_uid: Mapped[Optional[str]] = mapped_column(ForeignKey("charging_stations.uid"))
79
+ charging_station: Mapped[Optional["ChargingStation"]] = relationship("ChargingStation")
80
+ meter_start_kwh = Mapped[Optional[float]]
81
+ meter_end_kwh = Mapped[Optional[float]]
82
+ price_per_kwh = Mapped[Optional[float]]
83
+ price_per_min = Mapped[Optional[float]]
84
+ price_per_session = Mapped[Optional[float]]
85
+ real_charged = Mapped[Optional[float]]
86
+ real_cost = Mapped[Optional[float]]
71
87
  tags: Mapped[list["Tag"]] = relationship("Tag", secondary=charging_tag_association_table, backref=backref("charging_sessions"))
72
88
 
73
89
  # pylint: disable-next=too-many-arguments, too-many-positional-arguments
@@ -1,10 +1,10 @@
1
1
  """ This module contains the Vehicle charging state database model"""
2
2
  from __future__ import annotations
3
- from typing import Optional
3
+ from typing import TYPE_CHECKING, Optional
4
4
 
5
5
  from datetime import datetime
6
6
 
7
- from sqlalchemy import ForeignKey
7
+ from sqlalchemy import ForeignKey, UniqueConstraint
8
8
  from sqlalchemy.orm import Mapped, mapped_column, relationship
9
9
 
10
10
  from sqlalchemy_utc import UtcDateTime
@@ -13,6 +13,9 @@ from carconnectivity.charging import Charging
13
13
 
14
14
  from carconnectivity_plugins.database.model.base import Base
15
15
 
16
+ if TYPE_CHECKING:
17
+ from sqlalchemy import Constraint
18
+
16
19
 
17
20
  class ChargingState(Base): # pylint: disable=too-few-public-methods
18
21
  """
@@ -38,6 +41,7 @@ class ChargingState(Base): # pylint: disable=too-few-public-methods
38
41
  state (Optional[Charging.ChargingState]): The charging state value.
39
42
  """
40
43
  __tablename__: str = 'charging_states'
44
+ __table_args__: tuple[Constraint] = (UniqueConstraint("vin", "first_date", name="vin_first_date"),)
41
45
 
42
46
  id: Mapped[int] = mapped_column(primary_key=True)
43
47
  vin: Mapped[str] = mapped_column(ForeignKey("vehicles.vin"))