Skip to content
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion psa_car_controller/psacc/repository/config_repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@
minimum trip length =
# for future use
length unit = km
[Options]
# display units in the view layer: true = imperial (mi, mph), false = metric (km, km/h)
# data is always stored internally as metric
use imperial = false
[Electricity config]
# price by kw/h
day price =
Expand Down Expand Up @@ -68,7 +72,7 @@ def validate(cls, v):
if len(v) == 0:
return None

m = HOUR_REGEX.fullmatch(v.lower())
m = HOUR_REGEX.fullmatch(v)
if not m:
raise ValueError('invalid hour format')
hour = cls(v)
Expand All @@ -91,6 +95,10 @@ class GeneralConfig(BaseModel):
export_format = "csv"


class OptionsConfig(BaseModel):
use_imperial: bool = False


class ElectricityPriceConfig(BaseModel):
day_price: float = 0.15
night_price: float = None
Expand Down Expand Up @@ -153,6 +161,7 @@ def is_enable(self):

class ConfigRepository(BaseModel):
General: GeneralConfig
Options: OptionsConfig = OptionsConfig()
Electricity_config: ElectricityPriceConfig

@staticmethod
Expand Down
2 changes: 1 addition & 1 deletion psa_car_controller/web/assets/clientside.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ const logger = (function () {
return pub
}())
function addLocaleDate (data, dateKey) {
const dateOption = [undefined, { hour: 'numeric', minute: 'numeric' }]
const dateOption = [navigator.language, { hour: 'numeric', minute: 'numeric' }]
function dateToLocale (row, key) {
const date = new Date(row[key])
row[key] = date.getTime()
Expand Down
104 changes: 91 additions & 13 deletions psa_car_controller/web/figures.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,84 @@
AVG_CONSUM_PRICE = "avg_consum_price"
CURRENCY = "€"
EXPORT_FORMAT = "csv"
USE_IMPERIAL = False # view-layer flag: True = imperial (mi/mph), False = metric (km/km/h)

# Conversion factors (metric → imperial)
_KM_TO_MI = 0.621371
_KMH_TO_MPH = 0.621371
_KWH_PER_100KM_TO_KWH_PER_100MI = 1 / _KM_TO_MI # consuming more kWh over fewer miles


def _dist_unit() -> str:
return "mi" if USE_IMPERIAL else "km"


def _speed_unit() -> str:
return "mph" if USE_IMPERIAL else "km/h"


def _consumption_unit() -> str:
"""Electric consumption per distance unit."""
return f"kWh/100{_dist_unit()}"


def _fuel_consumption_unit() -> str:
return f"L/100{_dist_unit()}"


def _dist_suffix() -> str:
return f" {_dist_unit()}"


def _speed_suffix() -> str:
return f" {_speed_unit()}"


def convert_trips_for_display(trips_dict: list) -> list:
"""
Return a copy of the trips-as-dict list with distance, mileage,
speed_average, consumption_km, and consumption_fuel_km converted to
imperial values when USE_IMPERIAL is True. View-layer only.
"""
if not USE_IMPERIAL:
return trips_dict
result = []
for trip in trips_dict:
t = dict(trip)
if t.get("distance") is not None:
t["distance"] = t["distance"] * _KM_TO_MI
if t.get("mileage") is not None:
t["mileage"] = t["mileage"] * _KM_TO_MI
if t.get("speed_average") is not None:
t["speed_average"] = t["speed_average"] * _KMH_TO_MPH
if t.get("consumption_km") is not None:
t["consumption_km"] = t["consumption_km"] * _KWH_PER_100KM_TO_KWH_PER_100MI
if t.get("consumption_fuel_km") is not None:
t["consumption_fuel_km"] = t["consumption_fuel_km"] * _KWH_PER_100KM_TO_KWH_PER_100MI
result.append(t)
return result


def convert_chargings_for_display(chargings_dict: list) -> list:
"""
Return a copy of the chargings list with the mileage field converted
to imperial when USE_IMPERIAL is True. View-layer only.
"""
if not USE_IMPERIAL:
return chargings_dict
result = []
for charge in chargings_dict:
c = dict(charge)
if c.get("mileage") is not None:
c["mileage"] = c["mileage"] * _KM_TO_MI
result.append(c)
return result


def get_summary_cards():
return {"Average consumption": {"text": [card_value_div(AVG_CONSUM_KW, "kWh/100km"),
card_value_div(AVG_CONSUM_PRICE, f"{CURRENCY}/100km")],
dist = _dist_unit()
return {"Average consumption": {"text": [card_value_div(AVG_CONSUM_KW, f"kWh/100{dist}"),
card_value_div(AVG_CONSUM_PRICE, f"{CURRENCY}/100{dist}")],
"src": "assets/images/consumption.svg"},
"Average emission": {"text": [card_value_div(AVG_EMISSION_KM, " g/km"),
card_value_div(AVG_EMISSION_KW, "g/kWh")],
Expand All @@ -66,6 +139,8 @@ def get_figures(car: Car):
showlegend=False, name="Last Position"))
# table
nb_format = Format(precision=2, scheme=Scheme.fixed, symbol=Symbol.yes, group=Group.yes)
dist = _dist_unit()
speed = _speed_unit()
style_cell_conditional = []
if car.is_electric():
style_cell_conditional.append({'if': {'column_id': 'consumption_fuel_km', }, 'display': 'None', })
Expand All @@ -87,15 +162,15 @@ def get_figures(car: Car):
{'id': 'duration', 'name': 'duration', 'type': 'numeric',
'format': deepcopy(nb_format).symbol_suffix(" min").precision(0)},
{'id': 'speed_average', 'name': 'avg. speed', 'type': 'numeric',
'format': deepcopy(nb_format).symbol_suffix(" km/h").precision(0)},
'format': deepcopy(nb_format).symbol_suffix(f" {speed}").precision(0)},
{'id': 'consumption_km', 'name': 'avg. consumption', 'type': 'numeric',
'format': deepcopy(nb_format).symbol_suffix(" kWh/100km")},
'format': deepcopy(nb_format).symbol_suffix(f" kWh/100{dist}")},
{'id': 'consumption_fuel_km', 'name': 'average consumption fuel', 'type': 'numeric',
'format': deepcopy(nb_format).symbol_suffix(" L/100km")},
'format': deepcopy(nb_format).symbol_suffix(f" L/100{dist}")},
{'id': 'distance', 'name': 'distance', 'type': 'numeric',
'format': nb_format.symbol_suffix(" km").precision(1)},
{'id': 'mileage', 'name': 'mileage', 'type': 'numeric',
'format': nb_format},
'format': deepcopy(nb_format).symbol_suffix(f" {dist}").precision(1)},
{'id': 'mileage', 'name': f'mileage ({dist})', 'type': 'numeric',
'format': Format(precision=1, scheme=Scheme.fixed, symbol=Symbol.no, group=Group.yes)},
{'id': 'altitude_diff', 'name': 'altitude diff', 'type': 'numeric',
'format': deepcopy(nb_format).symbol_suffix(" m").precision(0)}
],
Expand All @@ -116,7 +191,7 @@ def get_figures(car: Car):
# consumption_fig
consumption_fig = px.histogram(x=[0], y=[1], title='Consumption of the car',
histfunc="avg")
consumption_fig.update_layout(yaxis_title="Consumption kWh/100Km", xaxis_title="date")
consumption_fig.update_layout(yaxis_title=f"Consumption kWh/100{dist}", xaxis_title="date")
consumption_fig.update_xaxes(type="date", tickformat="%d/%m/%Y")

consumption_fig_by_speed = px.histogram(data_frame=[{"start_at": 1, "speed_average": 2}], x="start_at",
Expand All @@ -126,7 +201,8 @@ def get_figures(car: Car):
consumption_fig_by_speed.update_layout(bargap=0.05)
consumption_fig_by_speed.add_trace(go.Scatter(mode="markers", x=[0],
y=[0], name="Trips"))
consumption_fig_by_speed.update_layout(xaxis_title="average Speed km/h", yaxis_title="Consumption kWh/100Km")
consumption_fig_by_speed.update_layout(xaxis_title=f"average Speed {speed}",
yaxis_title=f"Consumption kWh/100{dist}")
# battery_table
battery_table = DataTable(
id='battery-table',
Expand All @@ -153,7 +229,8 @@ def get_figures(car: Car):
{'id': 'price', 'name': 'price', 'type': 'numeric',
'format': deepcopy(nb_format).symbol_suffix(" " + CURRENCY).precision(2), 'editable': True},
{'id': 'charging_mode', 'name': 'charging mode', 'type': 'text'},
{'id': 'mileage', 'name': 'mileage', 'type': 'numeric', 'format': nb_format},
{'id': 'mileage', 'name': f'mileage ({dist})', 'type': 'numeric',
'format': Format(precision=1, scheme=Scheme.fixed, symbol=Symbol.no, group=Group.yes)},
],
data=[],
page_size=50,
Expand Down Expand Up @@ -197,7 +274,7 @@ def get_figures(car: Car):
go.Scatter(mode="markers", x=[0],
y=[0], name="Trips"))
consumption_fig_by_temp.update_layout(xaxis_title="average temperature in °C",
yaxis_title="Consumption kWh/100Km")
yaxis_title=f"Consumption kWh/100{dist}")
return True


Expand All @@ -220,5 +297,6 @@ def get_altitude_fig(trip: Trip):
for line in res:
line[0] = line[0] - start_mileage
fig = px.line(res, x=0, y=1)
fig.update_layout(xaxis_title="Distance km", yaxis_title="Altitude m")
dist = _dist_unit()
fig.update_layout(xaxis_title=f"Distance {dist}", yaxis_title="Altitude m")
return html.Div(Graph(figure=fig))
Loading
Loading