mirror of
https://github.com/flobz/psa_car_controller.git
synced 2026-08-22 17:36:15 +00:00
add tabs to dashboard, fix get_trips
This commit is contained in:
+11
-8
@@ -10,20 +10,20 @@ from json import JSONEncoder
|
||||
from hashlib import md5
|
||||
from time import sleep
|
||||
|
||||
import pytz
|
||||
from oauth2_client.credentials_manager import CredentialManager, ServiceInformation
|
||||
import paho.mqtt.client as mqtt
|
||||
from requests import Response
|
||||
from typing import List
|
||||
|
||||
import psa_connectedcar as psac
|
||||
from Trips import Trips
|
||||
from Trip import Trip
|
||||
from psa_connectedcar import ApiClient
|
||||
from psa_connectedcar.rest import ApiException
|
||||
from MyLogger import logger
|
||||
from threading import Semaphore, Timer
|
||||
from functools import wraps
|
||||
|
||||
from web.figures import convert_datetime
|
||||
import sqlite3
|
||||
|
||||
oauhth_url = {"clientsB2CPeugeot":"https://idpcvs.peugeot.com/am/oauth2/access_token",
|
||||
"clientsB2CCitroen":"https://idpcvs.citroen.com/am/oauth2/access_token",
|
||||
@@ -406,7 +406,6 @@ class MyPSACC:
|
||||
self._record_enabled = value
|
||||
|
||||
def record_position(self,vin, res:psac.models.status.Status):
|
||||
import sqlite3
|
||||
conn = sqlite3.connect('info.db')
|
||||
conn.execute(
|
||||
"CREATE TABLE IF NOT EXISTS position (Timestamp DATETIME PRIMARY KEY, VIN TEXT, longitude REAL, latitude REAL, mileage REAL, level INTEGER);")
|
||||
@@ -442,7 +441,7 @@ class MyPSACC:
|
||||
return geo_dumps(feature_collection, sort_keys=True)
|
||||
|
||||
@staticmethod
|
||||
def get_trips() -> List[Trips]:
|
||||
def get_trips() -> List[Trip]:
|
||||
sqlite3.register_converter("DATETIME", convert_datetime)
|
||||
conn = sqlite3.connect('info.db', detect_types=sqlite3.PARSE_DECLTYPES | sqlite3.PARSE_COLNAMES)
|
||||
conn.row_factory = sqlite3.Row
|
||||
@@ -450,11 +449,13 @@ class MyPSACC:
|
||||
start = res[0]
|
||||
end = res[1]
|
||||
trips = []
|
||||
tr = Trips()
|
||||
tr = Trip()
|
||||
battery_power = 46
|
||||
for next_el in res[2:]:
|
||||
distance = next_el["mileage"] - end["mileage"] # km
|
||||
if distance == 0:
|
||||
duration = (next_el["Timestamp"] - end["Timestamp"]).total_seconds()/3600
|
||||
speed = distance/duration
|
||||
if distance == 0 or speed < 1: # check the speed to handle missing point
|
||||
tr.distance = end["mileage"] - start["mileage"] # km
|
||||
if tr.distance > 0:
|
||||
tr.start_at = start["Timestamp"]
|
||||
@@ -469,7 +470,7 @@ class MyPSACC:
|
||||
f"{start['Timestamp']} {tr.distance:.1f}km {tr.duration:.2f}h {tr.speed_average:.2f} km/h {tr.consumption:.2f} kw {tr.consumption_km:.2f}kw/100km")
|
||||
trips.append(tr)
|
||||
start = next_el
|
||||
tr = Trips()
|
||||
tr = Trip()
|
||||
else:
|
||||
tr.add_points(end["longitude"], end["latitude"])
|
||||
end = next_el
|
||||
@@ -488,3 +489,5 @@ class MyPeugeotEncoder(JSONEncoder):
|
||||
return mpd
|
||||
|
||||
|
||||
def convert_datetime(st):
|
||||
return datetime.strptime(st.decode("utf-8"), "%Y-%m-%d %H:%M:%S+00:00").replace(tzinfo=pytz.UTC)
|
||||
@@ -0,0 +1,53 @@
|
||||
from typing import List
|
||||
from geojson import Feature, Point, FeatureCollection, MultiLineString
|
||||
from geojson import dumps as geo_dumps
|
||||
|
||||
|
||||
class Points():
|
||||
def __init__(self, latitude, longitude):
|
||||
self.latitude = latitude
|
||||
self.longitude = longitude
|
||||
|
||||
def list(self):
|
||||
return self.latitude, self.longitude
|
||||
|
||||
|
||||
class Trips(list):
|
||||
def __init__(self, *args):
|
||||
list.__init__(self, *args)
|
||||
|
||||
def to_geo_json(self):
|
||||
feature_collection = FeatureCollection(self)
|
||||
return feature_collection
|
||||
|
||||
|
||||
class Trip:
|
||||
def __init__(self):
|
||||
self.start_at = None
|
||||
self.end_at = None
|
||||
self.positions: List[Points] = []
|
||||
self.speed_average = None
|
||||
self.consumption = None
|
||||
self.consumption_km = None
|
||||
self.distance = None
|
||||
self.duration = None
|
||||
|
||||
def add_points(self, longitude, latitude):
|
||||
self.positions.append(Points(longitude, latitude))
|
||||
|
||||
def get_consumption(self):
|
||||
return {
|
||||
'date': self.start_at,
|
||||
'consumption': self.consumption_km,
|
||||
}
|
||||
|
||||
def to_geojson(self):
|
||||
multi_line_string = MultiLineString(tuple(map(list, self.positions)))
|
||||
return Feature(geometry=multi_line_string, properties={"start_at": self.start_at, "end_at": self.end_at,
|
||||
"average speed": self.speed_average,
|
||||
"average consumption": self.consumption_km})
|
||||
|
||||
def get_info(self):
|
||||
res = {"start_at": self.start_at.strftime("%x %X"), "end_at": self.end_at.strftime("%x %X"), "duration": self.duration*60,
|
||||
"speed_average": self.speed_average, "consumption_km": self.consumption_km, "distance": self.distance}
|
||||
return res
|
||||
@@ -1,25 +0,0 @@
|
||||
from typing import List
|
||||
class Points():
|
||||
def __init__(self, latitude, longitude):
|
||||
self.latitude = latitude
|
||||
self.longitude = longitude
|
||||
|
||||
class Trips:
|
||||
def __init__(self):
|
||||
self.start_at = None
|
||||
self.end_at = None
|
||||
self.positions: List[Points] = []
|
||||
self.speed_average = None
|
||||
self.consumption = None
|
||||
self.consumption_km = None
|
||||
|
||||
def add_points(self, longitude, latitude):
|
||||
self.positions.append(Points(longitude, latitude))
|
||||
|
||||
def get_consumption(self):
|
||||
return {
|
||||
'date': self.start_at,
|
||||
'consumption': self.consumption_km,
|
||||
}
|
||||
|
||||
|
||||
+14
-14
@@ -1,16 +1,16 @@
|
||||
paho-mqtt>=1.5.0
|
||||
dash>=1.18.0
|
||||
plotly>=4
|
||||
pandas
|
||||
oauth2_client
|
||||
python-dateutil
|
||||
Flask>=1.1.1
|
||||
certifi>=2018.4.16
|
||||
urllib3>=1.25.10
|
||||
six>=1.15.0
|
||||
requests>=2.24.0
|
||||
paho-mqtt>=1.5.0
|
||||
dash>=1.18.1
|
||||
numpy>=1.19.4
|
||||
pytz>=2019.3
|
||||
plotly>=4.5.0
|
||||
typing>=3.6.6
|
||||
pandas>=1.1.3
|
||||
argparse>=1.4.0
|
||||
androguard>=3.3.5
|
||||
certifi
|
||||
urllib3
|
||||
six
|
||||
requests
|
||||
numpy
|
||||
pytz
|
||||
typing
|
||||
argparse
|
||||
flask
|
||||
dash_bootstrap_components
|
||||
+35
-30
@@ -6,15 +6,21 @@ import dash_bootstrap_components as dbc
|
||||
from dash.dependencies import Output, Input
|
||||
import dash_core_components as dcc
|
||||
import dash_html_components as html
|
||||
|
||||
import locale
|
||||
from MyLogger import logger
|
||||
from flask import jsonify, request, Response as FlaskResponse
|
||||
|
||||
from web import figures
|
||||
|
||||
from MyPSACC import MyPSACC
|
||||
try:
|
||||
locale = locale.getlocale()[0].split("_")[0]
|
||||
locale_url = [f"https://cdn.plot.ly/plotly-locale-{locale}-latest.js"]
|
||||
dash_app = dash.Dash(external_stylesheets=[dbc.themes.BOOTSTRAP], external_scripts=locale_url, title="My car info")
|
||||
except:
|
||||
logger.warn("Can't get language")
|
||||
dash_app = dash.Dash(external_stylesheets=[dbc.themes.BOOTSTRAP])
|
||||
|
||||
dash_app = dash.Dash(external_stylesheets=[dbc.themes.BOOTSTRAP])
|
||||
app = dash_app.server
|
||||
myp = None
|
||||
chc = None
|
||||
@@ -116,35 +122,34 @@ def save_config(my_peugeot: MyPSACC):
|
||||
|
||||
trips = MyPSACC.get_trips()
|
||||
figures.get_figures(trips)
|
||||
dash_app.layout = dbc.Container([
|
||||
dash_app.layout = dbc.Container(fluid=True, children=[
|
||||
html.H1('My car info'),
|
||||
html.Hr(),
|
||||
html.Div([
|
||||
dcc.RangeSlider(
|
||||
id='date-slider',
|
||||
min=figures.unix_time_millis(figures.consumption_df["date"].min()),
|
||||
max=figures.unix_time_millis(figures.consumption_df["date"].max()),
|
||||
step=None,
|
||||
marks=figures.get_marks_from_start_end(figures.consumption_df["date"].min(),
|
||||
figures.consumption_df["date"].max()),
|
||||
value=[figures.unix_time_millis(figures.consumption_df["date"].min()),
|
||||
figures.unix_time_millis(figures.consumption_df["date"].max())],
|
||||
),
|
||||
html.Div(id='updatemode-output-container', style={'margin-top': 20})
|
||||
],
|
||||
style={'margin-left': 20}
|
||||
dcc.RangeSlider(
|
||||
id='date-slider',
|
||||
min=figures.unix_time_millis(figures.consumption_df["date"].min()),
|
||||
max=figures.unix_time_millis(figures.consumption_df["date"].max()),
|
||||
step=None,
|
||||
marks=figures.get_marks_from_start_end(figures.consumption_df["date"].min(),
|
||||
figures.consumption_df["date"].max()),
|
||||
value=[figures.unix_time_millis(figures.consumption_df["date"].min()),
|
||||
figures.unix_time_millis(figures.consumption_df["date"].max())],
|
||||
),
|
||||
html.Div([
|
||||
html.Div([
|
||||
dcc.Graph(figure=figures.trips_map, id="trips_map")
|
||||
]),
|
||||
html.Div([
|
||||
dcc.Graph(figure=figures.consumption_fig, id="consumption_fig")
|
||||
]),
|
||||
html.H2(id="consumption",
|
||||
children="Average consumption: {:.1f} kW/100km".format(float(figures.consumption_df.mean()))),
|
||||
html.Div([
|
||||
dcc.Graph(figure=figures.consumption_fig_by_speed, id="consumption_fig_by_speed")
|
||||
]),
|
||||
], id="data-div"),
|
||||
dbc.Tabs([
|
||||
dbc.Tab(label="Summary", tab_id="summary", children=[
|
||||
html.H2(id="consumption",
|
||||
children="Average consumption: {:.1f} kW/100km".format(
|
||||
float(figures.consumption_df.mean()))),
|
||||
dcc.Graph(figure=figures.consumption_fig, id="consumption_fig"),
|
||||
dcc.Graph(figure=figures.consumption_fig_by_speed, id="consumption_fig_by_speed")
|
||||
]),
|
||||
dbc.Tab(label="Trips", tab_id="trips", children=[figures.table_fig]),
|
||||
dbc.Tab(label="Map", tab_id="map", children=[
|
||||
dcc.Graph(figure=figures.trips_map, id="trips_map", style={"height": '90vh'})]),
|
||||
],
|
||||
id="tabs",
|
||||
active_tab="summary",
|
||||
),
|
||||
html.Div(id="tab-content", className="p-4"),
|
||||
]),
|
||||
])
|
||||
|
||||
+23
-12
@@ -1,12 +1,15 @@
|
||||
from datetime import datetime
|
||||
from copy import deepcopy
|
||||
from typing import List
|
||||
|
||||
import dash_table
|
||||
import numpy as np
|
||||
import pytz
|
||||
from dash_table.Format import Format, Scheme, Symbol
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from pandas import DataFrame
|
||||
import plotly.express as px
|
||||
import plotly.graph_objects as go
|
||||
from Trips import Trips
|
||||
from Trip import Trip
|
||||
from pandas import options as pandas_options
|
||||
|
||||
|
||||
def unix_time_millis(dt):
|
||||
@@ -36,18 +39,16 @@ def get_marks_from_start_end(start, end):
|
||||
return marks
|
||||
|
||||
|
||||
def convert_datetime(st):
|
||||
return datetime.strptime(st.decode("utf-8"), "%Y-%m-%d %H:%M:%S+00:00").replace(tzinfo=pytz.UTC)
|
||||
|
||||
|
||||
consumption_fig = None
|
||||
consumption_df = None
|
||||
trips_map = None
|
||||
consumption_fig_by_speed = None
|
||||
table_fig = None
|
||||
pandas_options.display.float_format = '${:.2f}'.format
|
||||
|
||||
|
||||
def get_figures(trips: List[Trips]):
|
||||
global consumption_fig, consumption_df, trips_map, consumption_fig_by_speed
|
||||
def get_figures(trips: List[Trip]):
|
||||
global consumption_fig, consumption_df, trips_map, consumption_fig_by_speed, table_fig
|
||||
lats = []
|
||||
lons = []
|
||||
names = []
|
||||
@@ -60,6 +61,19 @@ def get_figures(trips: List[Trips]):
|
||||
lons = np.append(lons, None)
|
||||
names = np.append(names, None)
|
||||
|
||||
# table
|
||||
nb_format = Format(precision=2, scheme=Scheme.fixed, symbol=Symbol.yes)
|
||||
table_fig = dash_table.DataTable(
|
||||
id='trips-table',
|
||||
sort_action='native',
|
||||
columns=[{'id': 'start_at', 'name': 'start at', 'type': 'datetime'},
|
||||
{'id': 'duration', 'name': 'duration', 'type': 'numeric', 'format': deepcopy(nb_format).symbol_suffix(" min").precision(0)},
|
||||
{'id': 'speed_average', 'name': 'average speed', 'type': 'numeric', 'format': deepcopy(nb_format).symbol_suffix(" km/h")},
|
||||
{'id': 'consumption_km', 'name': 'average consumption', 'type': 'numeric', 'format': deepcopy(nb_format).symbol_suffix(" kw/100km")},
|
||||
{'id': 'distance', 'name': 'distance', 'type': 'numeric', 'format': nb_format.symbol_suffix(" km")}],
|
||||
data=[tr.get_info() for tr in trips],
|
||||
)
|
||||
# map
|
||||
trips_map = px.line_mapbox(lat=lats, lon=lons, hover_name=names,
|
||||
mapbox_style="stamen-terrain", zoom=12)
|
||||
consumption_df = DataFrame.from_records([tr.get_consumption() for tr in trips])
|
||||
@@ -75,6 +89,3 @@ def get_figures(trips: List[Trips]):
|
||||
go.Scatter(mode="markers", x=consum_df_by_speed["speed"], y=consum_df_by_speed["value"],
|
||||
name="Trips"))
|
||||
consumption_fig_by_speed.update_layout(xaxis_title="average Speed km/h", yaxis_title="Consumption kW/100Km")
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user