mirror of
https://github.com/flobz/psa_car_controller.git
synced 2026-08-21 17:06:18 +00:00
improve trips algo
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import List
|
||||
|
||||
from geojson import Feature, FeatureCollection, MultiLineString
|
||||
@@ -68,18 +70,27 @@ class Trips(list):
|
||||
feature_collection = FeatureCollection(self)
|
||||
return feature_collection
|
||||
|
||||
def get_long_trips(self):
|
||||
res = []
|
||||
for tr in self:
|
||||
if tr.consumption > 1.8:
|
||||
res.append({"speed": tr.speed_average, "consumption_km": tr.consumption_km, "date": tr.start_at,
|
||||
"consumption": tr.consumption})
|
||||
return res
|
||||
|
||||
@staticmethod
|
||||
def get_trips(vehicles_list: Cars) -> List[Trip]:
|
||||
def get_trips(vehicles_list: Cars) -> dict[str, Trips]:
|
||||
conn = get_db()
|
||||
vehicles = conn.execute(
|
||||
"SELECT DISTINCT vin FROM position;").fetchall()
|
||||
trips_by_vin = {}
|
||||
for vin in vehicles:
|
||||
trips = Trips()
|
||||
vin = vin[0]
|
||||
car = vehicles_list.get_car_by_vin(vin)
|
||||
battery_capacity = car.battery_power
|
||||
fuel_capacity = car.fuel_capacity
|
||||
res = conn.execute('SELECT * FROM position ORDER BY Timestamp').fetchall()
|
||||
trips = []
|
||||
res = conn.execute('SELECT * FROM position WHERE VIN=? ORDER BY Timestamp', (vin,)).fetchall()
|
||||
if len(res) > 1:
|
||||
start = res[0]
|
||||
end = res[1]
|
||||
@@ -104,7 +115,7 @@ class Trips(list):
|
||||
if refuel > 0:
|
||||
restart_trip = True
|
||||
logger.debugv("refuel detected")
|
||||
elif distance == 0 and charge > 2:
|
||||
elif charge > 0:
|
||||
restart_trip = True
|
||||
logger.debugv("charge detected")
|
||||
elif speed_average < 0.2 and duration > 0.05: # think again if duration is really needed
|
||||
@@ -133,7 +144,7 @@ class Trips(list):
|
||||
if refuel > 0:
|
||||
end_trip = True
|
||||
logger.debugv("refuel detected")
|
||||
elif distance == 0 and charge > 2:
|
||||
elif charge > 0:
|
||||
end_trip = True
|
||||
logger.debugv("charge detected")
|
||||
elif speed_average < 0.2 and duration > 0.05:
|
||||
@@ -165,12 +176,14 @@ class Trips(list):
|
||||
if start["level_fuel"] is not None and end["level_fuel"] is not None:
|
||||
diff_level_fuel = start["level_fuel"] - end["level_fuel"]
|
||||
tr.consumption_fuel = round(diff_level_fuel / 100 * fuel_capacity, 2) # L
|
||||
tr.consumption_fuel_km = round(100 * tr.consumption_fuel / tr.distance, 2) # L/100 km
|
||||
tr.consumption_fuel_km = round(100 * tr.consumption_fuel / tr.distance,
|
||||
2) # L/100 km
|
||||
else:
|
||||
tr.consumption_fuel = 0
|
||||
tr.consumption_fuel_km = 0
|
||||
tr.mileage = end["mileage"]
|
||||
logger.debugv("Trip: %s -> %s %.1fkm %.2fh %.0fkm/h %.2fkWh %.2fkWh/100km %.2fL %.2fL/100km %.1fkm",
|
||||
logger.debugv("Trip: %s -> %s %.1fkm %.2fh %.0fkm/h %.2fkWh %.2fkWh/100km %.2fL "
|
||||
"%.2fL/100km %.1fkm",
|
||||
tr.start_at, tr.end_at, tr.distance, tr.duration,
|
||||
tr.speed_average, tr.consumption, tr.consumption_km,
|
||||
tr.consumption_fuel, tr.consumption_fuel_km, tr.mileage)
|
||||
@@ -185,4 +198,5 @@ class Trips(list):
|
||||
else:
|
||||
tr.add_points(end["longitude"], end["latitude"])
|
||||
end = next_el
|
||||
return trips
|
||||
trips_by_vin[vin] = trips
|
||||
return trips_by_vin
|
||||
|
||||
+2
-2
@@ -25,7 +25,7 @@ def start_app(title, base_path, debug: bool, host, port):
|
||||
try:
|
||||
lang = locale.getlocale()[0].split("_")[0]
|
||||
locale_url = [f"https://cdn.plot.ly/plotly-locale-{lang}-latest.js"]
|
||||
except:
|
||||
except IndexError:
|
||||
locale_url = None
|
||||
logger.warning("Can't get language")
|
||||
app = Flask(__name__)
|
||||
@@ -39,7 +39,7 @@ def start_app(title, base_path, debug: bool, host, port):
|
||||
dash_app = dash.Dash(external_stylesheets=[dbc.themes.BOOTSTRAP], external_scripts=locale_url, title=title,
|
||||
server=app, requests_pathname_prefix=requests_pathname_prefix)
|
||||
# keep this line
|
||||
import web.callback
|
||||
import web.views
|
||||
return run_simple(host, port, application, use_reloader=False, use_debugger=debug)
|
||||
|
||||
|
||||
|
||||
+40
-14
@@ -9,10 +9,11 @@ from dateutil.relativedelta import relativedelta
|
||||
from pandas import DataFrame
|
||||
import plotly.express as px
|
||||
import plotly.graph_objects as go
|
||||
from Trip import Trip
|
||||
from Trip import Trips
|
||||
from pandas import options as pandas_options
|
||||
import dash_html_components as html
|
||||
|
||||
|
||||
def unix_time_millis(dt):
|
||||
return int(dt.timestamp())
|
||||
|
||||
@@ -51,7 +52,7 @@ info = ""
|
||||
battery_info = dbc.Alert("No data to show", color="danger")
|
||||
|
||||
|
||||
def get_figures(trips: List[Trip], charging: List[dict]):
|
||||
def get_figures(trips: Trips, charging: List[dict]):
|
||||
global consumption_fig, consumption_df, trips_map, consumption_fig_by_speed, table_fig, info, battery_info
|
||||
lats = []
|
||||
lons = []
|
||||
@@ -81,26 +82,26 @@ def get_figures(trips: List[Trip], charging: List[dict]):
|
||||
'format': deepcopy(nb_format).symbol_suffix(" kWh/100km")},
|
||||
{'id': 'consumption_fuel_km', 'name': 'average consumption fuel', 'type': 'numeric',
|
||||
'format': deepcopy(nb_format).symbol_suffix(" L/100km")},
|
||||
{'id': 'distance', 'name': 'distance', 'type': 'numeric', 'format': nb_format.symbol_suffix(" km").precision(1)},
|
||||
{'id': 'mileage', 'name': 'mileage', 'type': 'numeric', 'format': nb_format.symbol_suffix(" km").precision(1)}],
|
||||
{'id': 'distance', 'name': 'distance', 'type': 'numeric',
|
||||
'format': nb_format.symbol_suffix(" km").precision(1)},
|
||||
{'id': 'mileage', 'name': 'mileage', 'type': 'numeric',
|
||||
'format': nb_format.symbol_suffix(" km").precision(1)}],
|
||||
data=[tr.get_info() for tr in trips],
|
||||
)
|
||||
# consumption_fig
|
||||
consumption_df = DataFrame.from_records([tr.get_consumption() for tr in trips])
|
||||
consumption_df = DataFrame.from_records(trips.get_long_trips())
|
||||
consumption_fig = px.line(consumption_df, x="date", y="consumption", title='Consumption of the car')
|
||||
consumption_fig.update_layout(yaxis_title="Consumption kWh/100Km")
|
||||
|
||||
consum_df_by_speed = DataFrame.from_records(
|
||||
[{"speed": tr.speed_average, "consumption": tr.consumption_km} for tr in trips])
|
||||
consumption_fig_by_speed = px.histogram(consum_df_by_speed, x="speed", y="consumption", histfunc="avg",
|
||||
consumption_fig_by_speed = px.histogram(consumption_df, x="speed", y="consumption_km", histfunc="avg",
|
||||
title="Consumption by speed")
|
||||
consumption_fig_by_speed.update_traces(xbins_size=15)
|
||||
consumption_fig_by_speed.update_layout(bargap=0.05)
|
||||
consumption_fig_by_speed.add_trace(
|
||||
go.Scatter(mode="markers", x=consum_df_by_speed["speed"], y=consum_df_by_speed["consumption"],
|
||||
go.Scatter(mode="markers", x=consumption_df["speed"], y=consumption_df["consumption_km"],
|
||||
name="Trips"))
|
||||
consumption_fig_by_speed.update_layout(xaxis_title="average Speed km/h", yaxis_title="Consumption kWh/100Km")
|
||||
kw_per_km = float(consumption_df.mean(numeric_only=True))
|
||||
kw_per_km = float(consumption_df["consumption_km"].mean())
|
||||
info = "Average consumption: {:.1f} kWh/100km".format(kw_per_km)
|
||||
|
||||
# charging
|
||||
@@ -119,7 +120,32 @@ def get_figures(trips: List[Trip], charging: List[dict]):
|
||||
charge_speed = 0
|
||||
except KeyError: # when there is no data yet:
|
||||
charge_speed = 0
|
||||
battery_info = html.Div(children=[
|
||||
html.P("Average C02 emission: {:.1f} g/kWh".format(co2_per_kw)),
|
||||
html.P("Average CO2 emission: {:.1f} g/km".format(co2_per_km)),
|
||||
html.P("Average charge speed: {:.3f} kW".format(charge_speed))])
|
||||
|
||||
battery_info = dash_table.DataTable(
|
||||
id='battery_info',
|
||||
sort_action='native',
|
||||
columns=[{'id': 'name', 'name': ''},
|
||||
{'id': 'value', 'name': ''}],
|
||||
style_header={'display': 'none'},
|
||||
style_data={'border': '0px'},
|
||||
data=[{"name": "Average emission:", "value": "{:.1f} g/km".format(co2_per_km)},
|
||||
{"name": " ", "value:": "{:.1f} g/kWh".format(co2_per_kw)},
|
||||
{"name": "Average charge speed:", "value": "{:.3f} kW".format(charge_speed)}])
|
||||
battery_info = html.Div(children=[html.Tr(
|
||||
[
|
||||
html.Td('Average emission:', rowSpan=2),
|
||||
html.Td("{:.1f} g/km".format(co2_per_km)),
|
||||
]
|
||||
),
|
||||
html. Tr(
|
||||
[
|
||||
"{:.1f} g/kWh".format(co2_per_kw),
|
||||
]
|
||||
),
|
||||
html.Tr(
|
||||
[
|
||||
html.Td("Average charge speed:"),
|
||||
html.Td("{:.3f} kW".format(charge_speed))
|
||||
]
|
||||
)
|
||||
])
|
||||
|
||||
@@ -34,7 +34,7 @@ min_date = max_date = min_millis = max_millis = step = marks = None
|
||||
def display_value(value):
|
||||
mini = datetime.fromtimestamp(value[0], tz=timezone.utc)
|
||||
maxi = datetime.fromtimestamp(value[1], tz=timezone.utc)
|
||||
filtered_trips = []
|
||||
filtered_trips = Trips()
|
||||
for trip in trips:
|
||||
if mini <= trip.start_at <= maxi:
|
||||
filtered_trips.append(trip)
|
||||
@@ -91,8 +91,8 @@ def get_position(vin):
|
||||
"url": f"http://maps.google.com/maps?q={latitude},{longitude}"})
|
||||
longitude, latitude = coordinates
|
||||
return jsonify(
|
||||
{"longitude": longitude, "latitude": latitude,
|
||||
"url": f"http://maps.google.com/maps?q={latitude},{longitude}"})
|
||||
{"longitude": longitude, "latitude": latitude,
|
||||
"url": f"http://maps.google.com/maps?q={latitude},{longitude}"})
|
||||
|
||||
|
||||
# Set a battery threshold and schedule an hour to stop the charge
|
||||
@@ -127,7 +127,8 @@ def update_trips():
|
||||
global trips, chargings
|
||||
logger.info("update_data")
|
||||
try:
|
||||
trips = Trips.get_trips(myp.vehicles_list)
|
||||
trips_by_vin = Trips.get_trips(myp.vehicles_list)
|
||||
trips = next(iter(trips_by_vin.values())) # todo handle multiple car
|
||||
chargings = MyPSACC.get_chargings()
|
||||
except:
|
||||
logger.error("update_trips: %s", traceback.format_exc())
|
||||
@@ -175,7 +176,7 @@ try:
|
||||
html.Div(id="tab-content", className="p-4"),
|
||||
])])
|
||||
except (IndexError, TypeError):
|
||||
logger.debug("Failed to generate figure, there is probably not enough data yet")
|
||||
logger.debug("Failed to generate figure, there is probably not enough data yet %s", traceback.format_exc())
|
||||
data_div = dbc.Alert("No data to show, there is probably no trips recorded yet", color="danger")
|
||||
|
||||
except:
|
||||
Reference in New Issue
Block a user