mirror of
https://github.com/flobz/psa_car_controller.git
synced 2026-08-21 17:06:18 +00:00
193 lines
6.9 KiB
Python
193 lines
6.9 KiB
Python
import json
|
|
import traceback
|
|
from datetime import datetime, timezone
|
|
import dash_bootstrap_components as dbc
|
|
from dash.dependencies import Output, Input
|
|
import dash_core_components as dcc
|
|
import dash_html_components as html
|
|
|
|
from MyLogger import logger
|
|
from flask import jsonify, request, Response as FlaskResponse
|
|
|
|
from MyPSACC import MyPSACC
|
|
from Trip import Trips
|
|
from web import figures
|
|
|
|
from web.app import app, dash_app, myp, chc
|
|
import web.db
|
|
|
|
trips: Trips
|
|
chargings: dict
|
|
min_date = max_date = min_millis = max_millis = step = marks = None
|
|
|
|
|
|
@dash_app.callback(Output('trips_map', 'figure'),
|
|
Output('consumption_fig', 'figure'),
|
|
Output('consumption_fig_by_speed', 'figure'),
|
|
Output('consumption_fig_by_temp', 'graph'),
|
|
Output('consumption', 'children'),
|
|
Output('tab_trips', 'children'),
|
|
Output('tab_battery', 'children'),
|
|
Output('tab_charge', 'children'),
|
|
Output('date-slider', 'max'),
|
|
Output('date-slider', 'step'),
|
|
Output('date-slider', 'marks'),
|
|
Input('date-slider', 'value'))
|
|
def display_value(value):
|
|
mini = datetime.fromtimestamp(value[0], tz=timezone.utc)
|
|
maxi = datetime.fromtimestamp(value[1], tz=timezone.utc)
|
|
filtered_trips = Trips()
|
|
for trip in trips:
|
|
if mini <= trip.start_at <= maxi:
|
|
filtered_trips.append(trip)
|
|
filtered_chargings = MyPSACC.get_chargings(mini, maxi)
|
|
figures.get_figures(filtered_trips, filtered_chargings)
|
|
consumption = "Average consumption: {:.1f} kWh/100km".format(float(figures.consumption_df["consumption_km"].mean()))
|
|
return figures.trips_map, figures.consumption_fig, figures.consumption_fig_by_speed,\
|
|
figures.consumption_graph_by_temp, consumption, figures.table_fig, figures.battery_info, \
|
|
figures.battery_table, max_millis, step, marks
|
|
|
|
|
|
@app.route('/getvehicles')
|
|
def get_vehicules():
|
|
return jsonify(myp.get_vehicles())
|
|
|
|
|
|
@app.route('/get_vehicleinfo/<string:vin>')
|
|
def get_vehicle_info(vin):
|
|
response = app.response_class(
|
|
response=json.dumps(myp.get_vehicle_info(vin).to_dict(), default=str),
|
|
status=200,
|
|
mimetype='application/json'
|
|
)
|
|
return response
|
|
|
|
|
|
@app.route('/charge_now/<string:vin>/<int:charge>')
|
|
def charge_now(vin, charge):
|
|
return jsonify(myp.charge_now(vin, charge != 0))
|
|
|
|
|
|
@app.route('/charge_hour')
|
|
def change_charge_hour():
|
|
return jsonify(myp.change_charge_hour(request.form['vin'], request.form['hour'], request.form['minute']))
|
|
|
|
|
|
@app.route('/wakeup/<string:vin>')
|
|
def wakeup(vin):
|
|
return jsonify(myp.wakeup(vin))
|
|
|
|
|
|
@app.route('/preconditioning/<string:vin>/<int:activate>')
|
|
def preconditioning(vin, activate):
|
|
return jsonify(myp.preconditioning(vin, activate))
|
|
|
|
|
|
@app.route('/position/<string:vin>')
|
|
def get_position(vin):
|
|
res = myp.get_vehicle_info(vin)
|
|
try:
|
|
coordinates = res.last_position.geometry.coordinates
|
|
except AttributeError:
|
|
return jsonify({'error': 'last_position not available from api'})
|
|
longitude, latitude, altitude = coordinates[:2]
|
|
if len(coordinates) == 3: # altitude is not always available
|
|
altitude = coordinates[2]
|
|
else:
|
|
altitude = None
|
|
return jsonify(
|
|
{"longitude": longitude, "latitude": latitude, "altitude": altitude,
|
|
"url": f"https://maps.google.com/maps?q={latitude},{longitude}"})
|
|
|
|
|
|
# Set a battery threshold and schedule an hour to stop the charge
|
|
@app.route('/charge_control')
|
|
def charge_control():
|
|
logger.info(request)
|
|
vin = request.args['vin']
|
|
charge_control = chc.get(vin)
|
|
if charge_control is None:
|
|
return jsonify("error: VIN not in list")
|
|
if 'hour' in request.args and 'minute' in request.args:
|
|
charge_control.set_stop_hour([int(request.args["hour"]), int(request.args["minute"])])
|
|
if 'percentage' in request.args:
|
|
charge_control.percentage_threshold = int(request.args['percentage'])
|
|
chc.save_config()
|
|
return jsonify(charge_control.get_dict())
|
|
|
|
|
|
@app.route('/positions')
|
|
def get_recorded_position():
|
|
return FlaskResponse(myp.get_recorded_position(), mimetype='application/json')
|
|
|
|
|
|
@app.after_request
|
|
def after_request(response):
|
|
header = response.headers
|
|
header['Access-Control-Allow-Origin'] = '*'
|
|
return response
|
|
|
|
|
|
def update_trips():
|
|
global trips, chargings
|
|
logger.info("update_data")
|
|
try:
|
|
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 (StopIteration, AssertionError):
|
|
logger.error("update_trips: %s", traceback.format_exc())
|
|
# update for slider
|
|
global min_date, max_date, min_millis, max_millis, step, marks
|
|
try:
|
|
min_date = trips[0].start_at
|
|
max_date = trips[-1].start_at
|
|
min_millis = figures.unix_time_millis(min_date)
|
|
max_millis = figures.unix_time_millis(max_date)
|
|
step = (max_millis - min_millis) / 100
|
|
marks = figures.get_marks_from_start_end(min_date, max_date)
|
|
except (ValueError, IndexError):
|
|
logger.error("update_trips (slider): %s", traceback.format_exc())
|
|
|
|
|
|
try:
|
|
web.db.callback_fct = update_trips
|
|
update_trips()
|
|
figures.get_figures(trips, chargings)
|
|
data_div = html.Div([dcc.RangeSlider(
|
|
id='date-slider',
|
|
min=min_millis,
|
|
max=max_millis,
|
|
step=step,
|
|
marks=marks,
|
|
value=[min_millis, max_millis],
|
|
),
|
|
html.Div([
|
|
dbc.Tabs([
|
|
dbc.Tab(label="Summary", tab_id="summary", children=[
|
|
html.H2(id="consumption",
|
|
children=figures.info),
|
|
dcc.Graph(figure=figures.consumption_fig, id="consumption_fig"),
|
|
dcc.Graph(figure=figures.consumption_fig_by_speed, id="consumption_fig_by_speed"),
|
|
figures.consumption_graph_by_temp
|
|
]),
|
|
dbc.Tab(label="Trips", tab_id="trips", id="tab_trips", children=[figures.table_fig]),
|
|
dbc.Tab(label="Battery", tab_id="battery", id="tab_battery", children=[figures.battery_info]),
|
|
dbc.Tab(label="Charge", tab_id="charge", id="tab_charge", children=[figures.battery_table]),
|
|
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"),
|
|
])])
|
|
except (IndexError, TypeError):
|
|
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")
|
|
|
|
dash_app.layout = dbc.Container(fluid=True, children=[
|
|
html.H1('My car info'),
|
|
data_div
|
|
])
|