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,144 @@
|
|
|
1
|
+
""" This module contains the Vehicle charging sessions database model"""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
from typing import TYPE_CHECKING, Optional
|
|
4
|
+
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
|
|
7
|
+
from sqlalchemy import ForeignKey, Table, Column, UniqueConstraint
|
|
8
|
+
from sqlalchemy.orm import Mapped, mapped_column, relationship, backref
|
|
9
|
+
|
|
10
|
+
from sqlalchemy_utc import UtcDateTime
|
|
11
|
+
|
|
12
|
+
from carconnectivity.charging import Charging
|
|
13
|
+
|
|
14
|
+
from carconnectivity_plugins.database.model.base import Base
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from sqlalchemy import Constraint
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
charging_tag_association_table = Table('charging_sessions_tags', Base.metadata,
|
|
22
|
+
Column('charging_sessions_id', ForeignKey('charging_sessions.id')),
|
|
23
|
+
Column('tags_name', ForeignKey('tags.name'))
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ChargingSession(Base): # pylint: disable=too-few-public-methods
|
|
28
|
+
"""
|
|
29
|
+
Represents a charging session for an electric vehicle.
|
|
30
|
+
This class models a complete charging session lifecycle, tracking various timestamps
|
|
31
|
+
and metrics from when the vehicle is plugged in until it is disconnected. It captures
|
|
32
|
+
connection events (plug, lock, charge start/end, unlock, unplug) along with session
|
|
33
|
+
metadata such as battery levels, charge type, location, and odometer readings.
|
|
34
|
+
Attributes:
|
|
35
|
+
id (int): Primary key identifier for the charging session.
|
|
36
|
+
vin (str): Vehicle Identification Number, foreign key to the vehicles table.
|
|
37
|
+
vehicle (Vehicle): Relationship to the associated Vehicle object.
|
|
38
|
+
plug_connected_date (datetime, optional): Timestamp when the plug was connected.
|
|
39
|
+
plug_locked_date (datetime, optional): Timestamp when the plug was locked.
|
|
40
|
+
plug_unlocked_date (datetime, optional): Timestamp when the plug was unlocked.
|
|
41
|
+
session_start_date (datetime, optional): Timestamp when charging started.
|
|
42
|
+
session_end_date (datetime, optional): Timestamp when charging ended.
|
|
43
|
+
plug_disconnected_date (datetime, optional): Timestamp when the plug was disconnected.
|
|
44
|
+
start_level (float, optional): Battery level percentage at session start.
|
|
45
|
+
end_level (float, optional): Battery level percentage at session end.
|
|
46
|
+
session_charge_type (Charging.ChargingType, optional): Type of charging (e.g., AC, DC).
|
|
47
|
+
session_position_latitude (float, optional): Latitude coordinate of charging location.
|
|
48
|
+
session_position_longitude (float, optional): Longitude coordinate of charging location.
|
|
49
|
+
session_odometer (float, optional): Odometer reading at session start.
|
|
50
|
+
charging_type (Charging.ChargingType, optional): General charging type used during session.
|
|
51
|
+
tags (list[Tag]): Associated tags for categorizing or labeling the session.
|
|
52
|
+
The class provides various helper methods to query the current and historical state
|
|
53
|
+
of the charging session, such as whether the vehicle is currently connected, locked,
|
|
54
|
+
or actively charging.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
__tablename__: str = 'charging_sessions'
|
|
58
|
+
__table_args__: tuple[Constraint] = (UniqueConstraint("vin", "session_start_date", name="vin_session_start_date"),)
|
|
59
|
+
|
|
60
|
+
id: Mapped[int] = mapped_column(primary_key=True)
|
|
61
|
+
vin: Mapped[str] = mapped_column(ForeignKey("vehicles.vin"))
|
|
62
|
+
vehicle: Mapped["Vehicle"] = relationship("Vehicle")
|
|
63
|
+
plug_connected_date: Mapped[Optional[datetime]] = mapped_column(UtcDateTime)
|
|
64
|
+
plug_locked_date: Mapped[Optional[datetime]] = mapped_column(UtcDateTime)
|
|
65
|
+
plug_unlocked_date: Mapped[Optional[datetime]] = mapped_column(UtcDateTime)
|
|
66
|
+
session_start_date: Mapped[Optional[datetime]] = mapped_column(UtcDateTime)
|
|
67
|
+
session_end_date: Mapped[Optional[datetime]] = mapped_column(UtcDateTime)
|
|
68
|
+
plug_disconnected_date: Mapped[Optional[datetime]] = mapped_column(UtcDateTime)
|
|
69
|
+
start_level: Mapped[Optional[float]]
|
|
70
|
+
end_level: Mapped[Optional[float]]
|
|
71
|
+
session_charge_type: Mapped[Optional[Charging.ChargingType]]
|
|
72
|
+
session_position_latitude: Mapped[Optional[float]]
|
|
73
|
+
session_position_longitude: Mapped[Optional[float]]
|
|
74
|
+
session_odometer: Mapped[Optional[float]]
|
|
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]]
|
|
87
|
+
tags: Mapped[list["Tag"]] = relationship("Tag", secondary=charging_tag_association_table, backref=backref("charging_sessions"))
|
|
88
|
+
|
|
89
|
+
# pylint: disable-next=too-many-arguments, too-many-positional-arguments
|
|
90
|
+
def __init__(self, vin: str, plug_connected_date: Optional[datetime] = None, plug_locked_date: Optional[datetime] = None,
|
|
91
|
+
session_start_date: Optional[datetime] = None, start_level: Optional[float] = None,
|
|
92
|
+
session_charge_type: Optional[Charging.ChargingType] = None, session_position_latitude: Optional[float] = None,
|
|
93
|
+
session_position_longitude: Optional[float] = None, session_odometer: Optional[float] = None,
|
|
94
|
+
charging_type: Optional[Charging.ChargingType] = None) -> None:
|
|
95
|
+
self.vin = vin
|
|
96
|
+
self.plug_connected_date = plug_connected_date
|
|
97
|
+
self.plug_locked_date = plug_locked_date
|
|
98
|
+
self.session_start_date = session_start_date
|
|
99
|
+
self.start_level = start_level
|
|
100
|
+
self.session_charge_type = session_charge_type
|
|
101
|
+
self.session_position_latitude = session_position_latitude
|
|
102
|
+
self.session_position_longitude = session_position_longitude
|
|
103
|
+
self.session_odometer = session_odometer
|
|
104
|
+
self.charging_type = charging_type
|
|
105
|
+
|
|
106
|
+
def is_connected(self) -> bool:
|
|
107
|
+
"""Returns True if the vehicle is currently connected to a charger."""
|
|
108
|
+
return self.plug_connected_date is not None and self.plug_disconnected_date is None
|
|
109
|
+
|
|
110
|
+
def is_locked(self) -> bool:
|
|
111
|
+
"""Returns True if the vehicle is currently locked to a charger."""
|
|
112
|
+
return self.plug_locked_date is not None and self.plug_unlocked_date is None
|
|
113
|
+
|
|
114
|
+
def is_charging(self) -> bool:
|
|
115
|
+
"""Returns True if the vehicle is currently in a charging session."""
|
|
116
|
+
return self.session_start_date is not None and self.session_end_date is None
|
|
117
|
+
|
|
118
|
+
def is_closed(self) -> bool:
|
|
119
|
+
"""Returns True if the charging session has ended."""
|
|
120
|
+
return self.session_end_date is not None or self.plug_unlocked_date is not None or self.plug_disconnected_date is not None
|
|
121
|
+
|
|
122
|
+
def was_started(self) -> bool:
|
|
123
|
+
"""Returns True if the charging session was started but may also have been already ended."""
|
|
124
|
+
return self.session_start_date is not None
|
|
125
|
+
|
|
126
|
+
def was_connected(self) -> bool:
|
|
127
|
+
"""Returns True if the vehicle was connected to a charger during this session."""
|
|
128
|
+
return self.plug_connected_date is not None
|
|
129
|
+
|
|
130
|
+
def was_locked(self) -> bool:
|
|
131
|
+
"""Returns True if the vehicle was locked to a charger during this session."""
|
|
132
|
+
return self.plug_locked_date is not None
|
|
133
|
+
|
|
134
|
+
def was_ended(self) -> bool:
|
|
135
|
+
"""Returns True if the charging session was ended."""
|
|
136
|
+
return self.session_end_date is not None
|
|
137
|
+
|
|
138
|
+
def was_disconnected(self) -> bool:
|
|
139
|
+
"""Returns True if the vehicle was disconnected from a charger during this session."""
|
|
140
|
+
return self.plug_disconnected_date is not None
|
|
141
|
+
|
|
142
|
+
def was_unlocked(self) -> bool:
|
|
143
|
+
"""Returns True if the vehicle was unlocked from a charger during this session."""
|
|
144
|
+
return self.plug_unlocked_date is not None
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
""" This module contains the Vehicle charging state database model"""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
from typing import TYPE_CHECKING, Optional
|
|
4
|
+
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
|
|
7
|
+
from sqlalchemy import ForeignKey, UniqueConstraint
|
|
8
|
+
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
9
|
+
|
|
10
|
+
from sqlalchemy_utc import UtcDateTime
|
|
11
|
+
|
|
12
|
+
from carconnectivity.charging import Charging
|
|
13
|
+
|
|
14
|
+
from carconnectivity_plugins.database.model.base import Base
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from sqlalchemy import Constraint
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ChargingState(Base): # pylint: disable=too-few-public-methods
|
|
21
|
+
"""
|
|
22
|
+
SQLAlchemy model representing a vehicle's charging state over a time period.
|
|
23
|
+
|
|
24
|
+
This model stores charging state information for vehicles, tracking the state
|
|
25
|
+
between a first and last date. It maintains a foreign key relationship with
|
|
26
|
+
the Vehicle model through the VIN (Vehicle Identification Number).
|
|
27
|
+
|
|
28
|
+
Attributes:
|
|
29
|
+
id (int): Primary key for the charging state record.
|
|
30
|
+
vin (str): Foreign key reference to the vehicle's VIN in the vehicles table.
|
|
31
|
+
vehicle (Vehicle): Relationship to the associated Vehicle model.
|
|
32
|
+
first_date (datetime): The start datetime of this charging state period (UTC).
|
|
33
|
+
last_date (datetime): The end datetime of this charging state period (UTC).
|
|
34
|
+
state (Optional[Charging.ChargingState]): The charging state during this period,
|
|
35
|
+
or None if no state is available.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
vin (str): The vehicle identification number.
|
|
39
|
+
first_date (datetime): The starting datetime for this charging state.
|
|
40
|
+
last_date (datetime): The ending datetime for this charging state.
|
|
41
|
+
state (Optional[Charging.ChargingState]): The charging state value.
|
|
42
|
+
"""
|
|
43
|
+
__tablename__: str = 'charging_states'
|
|
44
|
+
__table_args__: tuple[Constraint] = (UniqueConstraint("vin", "first_date", name="charging_states_vin_first_date"),)
|
|
45
|
+
|
|
46
|
+
id: Mapped[int] = mapped_column(primary_key=True)
|
|
47
|
+
vin: Mapped[str] = mapped_column(ForeignKey("vehicles.vin"))
|
|
48
|
+
vehicle: Mapped["Vehicle"] = relationship("Vehicle")
|
|
49
|
+
first_date: Mapped[datetime] = mapped_column(UtcDateTime)
|
|
50
|
+
last_date: Mapped[datetime] = mapped_column(UtcDateTime)
|
|
51
|
+
state: Mapped[Optional[Charging.ChargingState]]
|
|
52
|
+
|
|
53
|
+
def __init__(self, vin: str, first_date: datetime, last_date: datetime, state: Optional[Charging.ChargingState]) -> None:
|
|
54
|
+
self.vin = vin
|
|
55
|
+
self.first_date = first_date
|
|
56
|
+
self.last_date = last_date
|
|
57
|
+
self.state = state
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
""" This module contains the charging stations database model"""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
from typing import TYPE_CHECKING, Optional
|
|
4
|
+
|
|
5
|
+
from sqlalchemy.orm import Mapped, mapped_column
|
|
6
|
+
|
|
7
|
+
from carconnectivity_plugins.database.model.base import Base
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from carconnectivity.charging_station import ChargingStation as CarConnectivityChargingStation
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ChargingStation(Base): # pylint: disable=too-few-public-methods,too-many-instance-attributes
|
|
14
|
+
"""
|
|
15
|
+
SQLAlchemy model representing a charging station in the database.
|
|
16
|
+
This class maps charging station data to the 'charging_stations' database table,
|
|
17
|
+
storing information about electric vehicle charging locations and their properties.
|
|
18
|
+
Attributes:
|
|
19
|
+
uid (str): Unique identifier for the charging station (primary key).
|
|
20
|
+
source (Optional[str]): Source or provider of the charging station data.
|
|
21
|
+
name (Optional[str]): Name or title of the charging station.
|
|
22
|
+
latitude (Optional[float]): Geographic latitude coordinate of the station.
|
|
23
|
+
longitude (Optional[float]): Geographic longitude coordinate of the station.
|
|
24
|
+
address (Optional[str]): Physical address of the charging station.
|
|
25
|
+
max_power (Optional[float]): Maximum power output in kilowatts (kW).
|
|
26
|
+
num_spots (Optional[int]): Number of available charging spots.
|
|
27
|
+
operator_id (Optional[str]): Identifier of the charging station operator.
|
|
28
|
+
operator_name (Optional[str]): Name of the charging station operator.
|
|
29
|
+
raw (Optional[str]): Raw data from the original source in string format.
|
|
30
|
+
Methods:
|
|
31
|
+
from_carconnectivity_charging_station: Class method to create an instance from a
|
|
32
|
+
CarConnectivity ChargingStation object.
|
|
33
|
+
"""
|
|
34
|
+
__tablename__: str = 'charging_stations'
|
|
35
|
+
|
|
36
|
+
uid: Mapped[str] = mapped_column(primary_key=True)
|
|
37
|
+
source: Mapped[Optional[str]]
|
|
38
|
+
name: Mapped[Optional[str]]
|
|
39
|
+
latitude: Mapped[Optional[float]]
|
|
40
|
+
longitude: Mapped[Optional[float]]
|
|
41
|
+
address: Mapped[Optional[str]]
|
|
42
|
+
max_power: Mapped[Optional[float]]
|
|
43
|
+
num_spots: Mapped[Optional[int]]
|
|
44
|
+
operator_id: Mapped[Optional[str]]
|
|
45
|
+
operator_name: Mapped[Optional[str]]
|
|
46
|
+
raw: Mapped[Optional[str]]
|
|
47
|
+
|
|
48
|
+
# pylint: disable-next=too-many-arguments, too-many-positional-arguments
|
|
49
|
+
def __init__(self, uid: str) -> None:
|
|
50
|
+
self.uid = uid
|
|
51
|
+
|
|
52
|
+
@classmethod
|
|
53
|
+
def from_carconnectivity_charging_station(cls, charging_station: CarConnectivityChargingStation) -> ChargingStation:
|
|
54
|
+
"""Create a ChargingStation instance from a carconnectivity ChargingStation object."""
|
|
55
|
+
cs = cls(uid=charging_station.uid.value)
|
|
56
|
+
cs.source = charging_station.source.value
|
|
57
|
+
cs.name = charging_station.name.value
|
|
58
|
+
cs.latitude = charging_station.latitude.value
|
|
59
|
+
cs.longitude = charging_station.longitude.value
|
|
60
|
+
cs.address = charging_station.address.value
|
|
61
|
+
cs.max_power = charging_station.max_power.value
|
|
62
|
+
cs.num_spots = charging_station.num_spots.value
|
|
63
|
+
cs.operator_id = charging_station.operator_id.value
|
|
64
|
+
cs.operator_name = charging_station.operator_name.value
|
|
65
|
+
cs.raw = charging_station.raw.value
|
|
66
|
+
return cs
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
""" This module contains the Vehicle climatization state database model"""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
from typing import TYPE_CHECKING, Optional
|
|
4
|
+
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
|
|
7
|
+
from sqlalchemy import ForeignKey, UniqueConstraint
|
|
8
|
+
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
9
|
+
|
|
10
|
+
from sqlalchemy_utc import UtcDateTime
|
|
11
|
+
|
|
12
|
+
from carconnectivity.climatization import Climatization
|
|
13
|
+
|
|
14
|
+
from carconnectivity_plugins.database.model.base import Base
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from sqlalchemy import Constraint
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ClimatizationState(Base): # pylint: disable=too-few-public-methods
|
|
21
|
+
"""
|
|
22
|
+
SQLAlchemy model representing a vehicle's climatization state over a time period.
|
|
23
|
+
|
|
24
|
+
This model stores climatization state information for vehicles, tracking the state
|
|
25
|
+
between a first and last date. It maintains a foreign key relationship with
|
|
26
|
+
the Vehicle model through the VIN (Vehicle Identification Number).
|
|
27
|
+
|
|
28
|
+
Attributes:
|
|
29
|
+
id (int): Primary key for the climatization state record.
|
|
30
|
+
vin (str): Foreign key reference to the vehicle's VIN in the vehicles table.
|
|
31
|
+
vehicle (Vehicle): Relationship to the associated Vehicle model.
|
|
32
|
+
first_date (datetime): The start datetime of this climatization state period (UTC).
|
|
33
|
+
last_date (datetime): The end datetime of this climatization state period (UTC).
|
|
34
|
+
state (Optional[Climatization.ClimatizationState]): The climatization state during this period,
|
|
35
|
+
or None if no state is available.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
vin (str): The vehicle identification number.
|
|
39
|
+
first_date (datetime): The starting datetime for this climatization state.
|
|
40
|
+
last_date (datetime): The ending datetime for this climatization state.
|
|
41
|
+
state (Optional[Climatization.ClimatizationState]): The climatization state value.
|
|
42
|
+
"""
|
|
43
|
+
__tablename__: str = 'climatization_states'
|
|
44
|
+
__table_args__: tuple[Constraint] = (UniqueConstraint("vin", "first_date", name="climatization_states_vin_first_date"),)
|
|
45
|
+
|
|
46
|
+
id: Mapped[int] = mapped_column(primary_key=True)
|
|
47
|
+
vin: Mapped[str] = mapped_column(ForeignKey("vehicles.vin"))
|
|
48
|
+
vehicle: Mapped["Vehicle"] = relationship("Vehicle")
|
|
49
|
+
first_date: Mapped[datetime] = mapped_column(UtcDateTime)
|
|
50
|
+
last_date: Mapped[datetime] = mapped_column(UtcDateTime)
|
|
51
|
+
state: Mapped[Optional[Climatization.ClimatizationState]]
|
|
52
|
+
|
|
53
|
+
def __init__(self, vin: str, first_date: datetime, last_date: datetime, state: Optional[Climatization.ClimatizationState]) -> None:
|
|
54
|
+
self.vin = vin
|
|
55
|
+
self.first_date = first_date
|
|
56
|
+
self.last_date = last_date
|
|
57
|
+
self.state = state
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
""" This module contains the Vehicle connectionstate database model"""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
from typing import TYPE_CHECKING, Optional
|
|
4
|
+
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
|
|
7
|
+
from sqlalchemy import ForeignKey, UniqueConstraint
|
|
8
|
+
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
9
|
+
|
|
10
|
+
from sqlalchemy_utc import UtcDateTime
|
|
11
|
+
|
|
12
|
+
from carconnectivity.vehicle import GenericVehicle
|
|
13
|
+
|
|
14
|
+
from carconnectivity_plugins.database.model.base import Base
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from sqlalchemy import Constraint
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ConnectionState(Base): # pylint: disable=too-few-public-methods
|
|
21
|
+
"""
|
|
22
|
+
Represents a vehicle connection state record in the database.
|
|
23
|
+
This class models the states table which tracks the historical connection
|
|
24
|
+
state of vehicles over time periods.
|
|
25
|
+
Attributes:
|
|
26
|
+
id (int): Primary key for the state record.
|
|
27
|
+
vin (str): Foreign key reference to the vehicle identification number in the parent table.
|
|
28
|
+
vehicle (Vehicle): Relationship to the Vehicle model.
|
|
29
|
+
first_date (datetime): The timestamp when this state period began (UTC).
|
|
30
|
+
last_date (datetime, optional): The timestamp when this state period ended (UTC).
|
|
31
|
+
None indicates the state is still active.
|
|
32
|
+
state (GenericVehicle.State, optional): The vehicle's operational state during this period.
|
|
33
|
+
connection_state (GenericVehicle.ConnectionState, optional): The vehicle's connection state
|
|
34
|
+
during this period.
|
|
35
|
+
Args:
|
|
36
|
+
vin (str): The vehicle identification number.
|
|
37
|
+
first_date (datetime): The start timestamp of the state period.
|
|
38
|
+
last_date (datetime): The end timestamp of the state period.
|
|
39
|
+
state (GenericVehicle.State, optional): The vehicle's operational state.
|
|
40
|
+
connection_state (GenericVehicle.ConnectionState, optional): The vehicle's connection state.
|
|
41
|
+
"""
|
|
42
|
+
__tablename__: str = 'connection_states'
|
|
43
|
+
__table_args__: tuple[Constraint] = (UniqueConstraint("vin", "first_date", name="connection_states_vin_first_date"),)
|
|
44
|
+
|
|
45
|
+
id: Mapped[int] = mapped_column(primary_key=True)
|
|
46
|
+
vin: Mapped[str] = mapped_column(ForeignKey("vehicles.vin"))
|
|
47
|
+
vehicle: Mapped["Vehicle"] = relationship("Vehicle")
|
|
48
|
+
first_date: Mapped[datetime] = mapped_column(UtcDateTime)
|
|
49
|
+
last_date: Mapped[datetime] = mapped_column(UtcDateTime)
|
|
50
|
+
connection_state: Mapped[Optional[GenericVehicle.ConnectionState]]
|
|
51
|
+
|
|
52
|
+
# pylint: disable-next=too-many-arguments, too-many-positional-arguments
|
|
53
|
+
def __init__(self, vin: str, first_date: datetime, last_date: datetime, connection_state: Optional[GenericVehicle.ConnectionState]) -> None:
|
|
54
|
+
self.vin = vin
|
|
55
|
+
self.first_date = first_date
|
|
56
|
+
self.last_date = last_date
|
|
57
|
+
self.connection_state = connection_state
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""Decorator for handling datetime objects in SQLAlchemy models.
|
|
2
|
+
This decorator ensures that datetime values are stored in UTC and retrieved in UTC,
|
|
3
|
+
while also converting naive datetime objects to the local timezone before storing.
|
|
4
|
+
It is designed to work with SQLAlchemy's DateTime type."""
|
|
5
|
+
from datetime import datetime, timezone
|
|
6
|
+
import sqlalchemy
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
# pylint: disable=too-many-ancestors
|
|
10
|
+
class DatetimeDecorator(sqlalchemy.types.TypeDecorator):
|
|
11
|
+
"""Decorator for handling datetime objects in SQLAlchemy models."""
|
|
12
|
+
impl = sqlalchemy.types.DateTime
|
|
13
|
+
cache_ok = True
|
|
14
|
+
|
|
15
|
+
def process_literal_param(self, value, dialect):
|
|
16
|
+
"""Process literal parameter for SQLAlchemy."""
|
|
17
|
+
if value is None:
|
|
18
|
+
return None
|
|
19
|
+
return f"'{value.isoformat()}'"
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
def python_type(self):
|
|
23
|
+
"""Return the Python type handled by this decorator."""
|
|
24
|
+
return datetime
|
|
25
|
+
|
|
26
|
+
LOCAL_TIMEZONE = datetime.utcnow().astimezone().tzinfo
|
|
27
|
+
|
|
28
|
+
def process_bind_param(self, value, dialect):
|
|
29
|
+
if value is None:
|
|
30
|
+
return value
|
|
31
|
+
|
|
32
|
+
if value.tzinfo is None:
|
|
33
|
+
value = value.astimezone(self.LOCAL_TIMEZONE)
|
|
34
|
+
|
|
35
|
+
return value.astimezone(timezone.utc)
|
|
36
|
+
|
|
37
|
+
def process_result_value(self, value, dialect):
|
|
38
|
+
if value is None:
|
|
39
|
+
return value
|
|
40
|
+
|
|
41
|
+
if value.tzinfo is None:
|
|
42
|
+
return value.replace(tzinfo=timezone.utc)
|
|
43
|
+
|
|
44
|
+
return value.astimezone(timezone.utc)
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
""" This module contains the Drive database model"""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
from typing import TYPE_CHECKING, Optional
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
|
|
7
|
+
from sqlalchemy import ForeignKey, UniqueConstraint
|
|
8
|
+
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
9
|
+
|
|
10
|
+
from carconnectivity.drive import GenericDrive
|
|
11
|
+
|
|
12
|
+
from carconnectivity_plugins.database.agents.base_agent import BaseAgent
|
|
13
|
+
from carconnectivity_plugins.database.agents.drive_state_agent import DriveStateAgent
|
|
14
|
+
from carconnectivity_plugins.database.model.base import Base
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from sqlalchemy.orm import scoped_session
|
|
18
|
+
from sqlalchemy.orm.session import Session
|
|
19
|
+
from sqlalchemy import Constraint
|
|
20
|
+
|
|
21
|
+
from carconnectivity_plugins.database.plugin import Plugin
|
|
22
|
+
|
|
23
|
+
LOG: logging.Logger = logging.getLogger("carconnectivity.plugins.database.model.drive")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Drive(Base):
|
|
27
|
+
"""
|
|
28
|
+
Database model representing a vehicle drive/trip.
|
|
29
|
+
This class maps to the 'drives' table and stores information about individual
|
|
30
|
+
vehicle drives, including their relationship to vehicles and various drive
|
|
31
|
+
attributes tracked through CarConnectivity.
|
|
32
|
+
Attributes:
|
|
33
|
+
id (int): Primary key for the drive record.
|
|
34
|
+
vin (str): Vehicle Identification Number, foreign key to vehicles table.
|
|
35
|
+
vehicle (Vehicle): SQLAlchemy relationship to the associated Vehicle.
|
|
36
|
+
drive_id (Optional[str]): Optional identifier for the drive.
|
|
37
|
+
type (Optional[GenericDrive.Type]): Type/category of the drive.
|
|
38
|
+
carconnectivity_drive (Optional[GenericDrive]): Reference to the CarConnectivity
|
|
39
|
+
drive object (not persisted to database).
|
|
40
|
+
agents (list[BaseAgent]): List of agents monitoring this drive (not persisted
|
|
41
|
+
to database).
|
|
42
|
+
Args:
|
|
43
|
+
vin (str): Vehicle Identification Number to associate this drive with.
|
|
44
|
+
Methods:
|
|
45
|
+
connect: Establishes connection between database model and CarConnectivity
|
|
46
|
+
drive object, sets up observers, and initializes agents.
|
|
47
|
+
"""
|
|
48
|
+
__tablename__: str = 'drives'
|
|
49
|
+
__allow_unmapped__: bool = True
|
|
50
|
+
__table_args__: tuple[Constraint] = (UniqueConstraint("vin", "drive_id", name="vin_drive_id"),)
|
|
51
|
+
|
|
52
|
+
id: Mapped[int] = mapped_column(primary_key=True)
|
|
53
|
+
vin: Mapped[str] = mapped_column(ForeignKey("vehicles.vin"))
|
|
54
|
+
vehicle: Mapped["Vehicle"] = relationship("Vehicle")
|
|
55
|
+
drive_id: Mapped[Optional[str]]
|
|
56
|
+
type: Mapped[Optional[GenericDrive.Type]]
|
|
57
|
+
capacity: Mapped[Optional[float]]
|
|
58
|
+
capacity_total: Mapped[Optional[float]]
|
|
59
|
+
wltp_range: Mapped[Optional[float]]
|
|
60
|
+
|
|
61
|
+
carconnectivity_drive: Optional[GenericDrive] = None
|
|
62
|
+
agents: list[BaseAgent] = []
|
|
63
|
+
|
|
64
|
+
def __init__(self, vin, drive_id: Optional[str] = None) -> None:
|
|
65
|
+
self.vin = vin
|
|
66
|
+
self.drive_id = drive_id
|
|
67
|
+
|
|
68
|
+
def connect(self, database_plugin: Plugin, session_factory: scoped_session[Session], carconnectivity_drive: GenericDrive) -> None:
|
|
69
|
+
"""
|
|
70
|
+
Connect a CarConnectivity drive object to this database model instance.
|
|
71
|
+
This method establishes a connection between the database drive model and a CarConnectivity drive object,
|
|
72
|
+
sets up observers for type changes, synchronizes the drive type if available, and initializes the drive state agent.
|
|
73
|
+
Args:
|
|
74
|
+
session (Session): The database session to use for operations.
|
|
75
|
+
carconnectivity_drive (GenericDrive): The CarConnectivity drive object to connect to this database model.
|
|
76
|
+
Returns:
|
|
77
|
+
None
|
|
78
|
+
Note:
|
|
79
|
+
- Adds an observer to monitor type changes in the vehicle
|
|
80
|
+
- Automatically syncs the drive type if enabled and has a value
|
|
81
|
+
- Creates and registers a DriveStateAgent for managing drive state
|
|
82
|
+
"""
|
|
83
|
+
if self.carconnectivity_drive is not None:
|
|
84
|
+
raise ValueError("Can only connect once! Drive already connected with database model")
|
|
85
|
+
self.carconnectivity_drive = carconnectivity_drive
|
|
86
|
+
LOG.debug("Adding DriveStateAgent to drive %s of vehicle %s", self.drive_id, self.vin)
|
|
87
|
+
drive_state_agent: DriveStateAgent = DriveStateAgent(database_plugin, session_factory, self) # type: ignore[assignment]
|
|
88
|
+
self.agents.append(drive_state_agent)
|
|
89
|
+
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
""" This module contains the Drive consumption database model"""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
from typing import TYPE_CHECKING, Optional
|
|
4
|
+
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
|
|
7
|
+
from sqlalchemy import ForeignKey, UniqueConstraint
|
|
8
|
+
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
9
|
+
|
|
10
|
+
from sqlalchemy_utc import UtcDateTime
|
|
11
|
+
|
|
12
|
+
from carconnectivity_plugins.database.model.base import Base
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from sqlalchemy import Constraint
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class DriveConsumption(Base): # pylint: disable=too-few-public-methods
|
|
19
|
+
"""
|
|
20
|
+
SQLAlchemy model representing consumption data for a specific drive .
|
|
21
|
+
This model stores consumption measurements for a drive
|
|
22
|
+
tracking energy or fuel usage between two timestamps.
|
|
23
|
+
Attributes:
|
|
24
|
+
id (int): Primary key identifier for the drive consumption record.
|
|
25
|
+
drive_id (int): Foreign key reference to the associated Drive.
|
|
26
|
+
drive (Drive): Relationship to the parent Drive object.
|
|
27
|
+
first_date (datetime): Start timestamp of the consumption measurement period.
|
|
28
|
+
last_date (datetime): End timestamp of the consumption measurement period.
|
|
29
|
+
consumption (Optional[float]): Measured consumption value for the period (e.g., kWh, liters).
|
|
30
|
+
Table Constraints:
|
|
31
|
+
- Unique constraint on (drive_id, first_date) to prevent duplicate entries.
|
|
32
|
+
"""
|
|
33
|
+
__tablename__: str = 'drive_consumptions'
|
|
34
|
+
__table_args__: tuple[Constraint] = (UniqueConstraint("drive_id", "first_date", name="drive_consumptions_drive_id_first_date"),)
|
|
35
|
+
|
|
36
|
+
id: Mapped[int] = mapped_column(primary_key=True)
|
|
37
|
+
drive_id: Mapped[int] = mapped_column(ForeignKey("drives.id"))
|
|
38
|
+
drive: Mapped["Drive"] = relationship("Drive")
|
|
39
|
+
first_date: Mapped[datetime] = mapped_column(UtcDateTime)
|
|
40
|
+
last_date: Mapped[datetime] = mapped_column(UtcDateTime)
|
|
41
|
+
consumption: Mapped[Optional[float]]
|
|
42
|
+
|
|
43
|
+
# pylint: disable-next=too-many-arguments, too-many-positional-arguments
|
|
44
|
+
def __init__(self, drive_id: Mapped[int], first_date: datetime, last_date: datetime, consumption: Optional[float]) -> None:
|
|
45
|
+
self.drive_id = drive_id
|
|
46
|
+
self.first_date = first_date
|
|
47
|
+
self.last_date = last_date
|
|
48
|
+
self.consumption = consumption
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
""" This module contains the Drive level database model"""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
from typing import TYPE_CHECKING, Optional
|
|
4
|
+
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
|
|
7
|
+
from sqlalchemy import ForeignKey, UniqueConstraint
|
|
8
|
+
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
9
|
+
|
|
10
|
+
from sqlalchemy_utc import UtcDateTime
|
|
11
|
+
|
|
12
|
+
from carconnectivity_plugins.database.model.base import Base
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from sqlalchemy import Constraint
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class DriveLevel(Base): # pylint: disable=too-few-public-methods
|
|
19
|
+
"""
|
|
20
|
+
SQLAlchemy model representing energy levels for a drive.
|
|
21
|
+
This class maps to the 'drive_levels' database table and tracks the level.
|
|
22
|
+
Attributes:
|
|
23
|
+
id (int): Primary key identifier for the drive level record.
|
|
24
|
+
drive_id (str): Foreign key referencing the associated drive
|
|
25
|
+
drive (Drive): Relationship to the Drive model.
|
|
26
|
+
first_date (datetime): Timestamp when this level measurement started.
|
|
27
|
+
last_date (datetime): Timestamp when this level measurement ended.
|
|
28
|
+
level (Optional[float]): Battery level value (percentage or absolute value).
|
|
29
|
+
Args:
|
|
30
|
+
drive_id (str): The identifier of the associated drive.
|
|
31
|
+
first_date (datetime): Start timestamp for this level measurement.
|
|
32
|
+
last_date (datetime): End timestamp for this level measurement.
|
|
33
|
+
level (Optional[float]): The battery level value, can be None if unavailable.
|
|
34
|
+
"""
|
|
35
|
+
__tablename__: str = 'drive_levels'
|
|
36
|
+
__table_args__: tuple[Constraint] = (UniqueConstraint("drive_id", "first_date", name="drive_levels_drive_id_first_date"),)
|
|
37
|
+
|
|
38
|
+
id: Mapped[int] = mapped_column(primary_key=True)
|
|
39
|
+
drive_id: Mapped[int] = mapped_column(ForeignKey("drives.id"))
|
|
40
|
+
drive: Mapped["Drive"] = relationship("Drive")
|
|
41
|
+
first_date: Mapped[datetime] = mapped_column(UtcDateTime)
|
|
42
|
+
last_date: Mapped[datetime] = mapped_column(UtcDateTime)
|
|
43
|
+
level: Mapped[Optional[float]]
|
|
44
|
+
|
|
45
|
+
# pylint: disable-next=too-many-arguments, too-many-positional-arguments
|
|
46
|
+
def __init__(self, drive_id: Mapped[int], first_date: datetime, last_date: datetime, level: Optional[float]) -> None:
|
|
47
|
+
self.drive_id = drive_id
|
|
48
|
+
self.first_date = first_date
|
|
49
|
+
self.last_date = last_date
|
|
50
|
+
self.level = level
|