add charge price

This commit is contained in:
Florian Bezannier
2021-04-20 11:10:52 +02:00
parent 42411d8818
commit 98baca6601
9 changed files with 276 additions and 44 deletions
+5 -7
View File
@@ -6,6 +6,7 @@ from flask import Flask
import locale
from werkzeug import run_simple
try:
from werkzeug.middleware.dispatcher import DispatcherMiddleware
except ImportError:
@@ -18,6 +19,10 @@ from MyPSACC import MyPSACC
app = None
dash_app = None
dispatcher = None
# noinspection PyTypeChecker
myp:MyPSACC = None
# noinspection PyTypeChecker
chc: ChargeControls = None
def start_app(title, base_path, debug: bool, host, port):
global app, dash_app, dispatcher
@@ -42,13 +47,6 @@ def start_app(title, base_path, debug: bool, host, port):
import web.views
return run_simple(host, port, application, use_reloader=False, use_debugger=debug)
# noinspection PyTypeChecker
myp:MyPSACC = None
# noinspection PyTypeChecker
chc: ChargeControls = None
def save_config(my_peugeot: MyPSACC, name):
my_peugeot.save_config(name)
threading.Timer(30, save_config, args=[my_peugeot, name]).start()
+56 -6
View File
@@ -8,24 +8,40 @@ from MyLogger import logger
callback_fct: Callable[[], None] = lambda: None
default_db_file = 'info.db'
db_initialized = False
def convert_datetime(st):
return datetime.strptime(st.decode("utf-8"), "%Y-%m-%d %H:%M:%S+00:00").replace(tzinfo=pytz.UTC)
def convert_datetime_from_bytes(bytes_string):
return datetime.strptime(bytes_string.decode("utf-8"), "%Y-%m-%d %H:%M:%S+00:00").replace(tzinfo=pytz.UTC)
def convert_datetime_from_string(st):
return datetime.strptime(st, "%Y-%m-%dT%H:%M:%S+00:00").replace(tzinfo=pytz.UTC)
def update_callback():
callback_fct()
def get_db(db_file=default_db_file):
sqlite3.register_converter("DATETIME", convert_datetime)
conn = sqlite3.connect(db_file, detect_types=sqlite3.PARSE_DECLTYPES | sqlite3.PARSE_COLNAMES)
conn.row_factory = sqlite3.Row
def set_db_callback(callbackfct):
global callback_fct
callback_fct = callbackfct
def backup(conn):
back_conn = sqlite3.connect("info_backup.db")
conn.backup(back_conn)
back_conn.close()
def init_db(conn):
global db_initialized
conn.execute("CREATE TABLE IF NOT EXISTS position (Timestamp DATETIME PRIMARY KEY, VIN TEXT, longitude REAL, "
"latitude REAL, mileage REAL, level INTEGER, level_fuel INTEGER, moving BOOLEAN, temperature INTEGER);")
make_backup = False
try:
conn.execute("ALTER TABLE position ADD level_fuel INTEGER;")
make_backup = True
except sqlite3.OperationalError:
pass
conn.execute("CREATE TABLE IF NOT EXISTS battery (start_at DATETIME PRIMARY KEY,stop_at DATETIME,VIN TEXT, "
@@ -33,10 +49,34 @@ def get_db(db_file=default_db_file):
conn.create_function("update_trips", 0, update_callback)
conn.execute("CREATE TEMP TRIGGER IF NOT EXISTS update_trigger AFTER INSERT ON position BEGIN "
"SELECT update_trips(); END;")
try:
conn.execute("ALTER TABLE battery ADD price INTEGER;")
make_backup = True
except sqlite3.OperationalError:
pass
if make_backup:
backup(conn)
clean_battery(conn)
conn.commit()
db_initialized = True
def get_db(db_file=default_db_file):
sqlite3.register_converter("DATETIME", convert_datetime_from_bytes)
conn = sqlite3.connect(db_file, detect_types=sqlite3.PARSE_DECLTYPES | sqlite3.PARSE_COLNAMES)
conn.row_factory = sqlite3.Row
if not db_initialized:
init_db(conn)
return conn
def clean_battery(conn):
# delete charging longer than 17h
conn.execute("DElETE FROM battery WHERE JULIANDAY(stop_at)-JULIANDAY(start_at)>0.7;")
conn.execute("DELETE FROM battery WHERE start_level==end_level;")
conn.commit()
def clean_position(conn):
res = conn.execute(
"SELECT Timestamp,mileage,level from position ORDER BY Timestamp DESC LIMIT 3;").fetchall()
@@ -55,3 +95,13 @@ def get_last_temp(vin):
if res is None:
return None
return res[0]
def set_chargings_price(conn, start_at, price):
if isinstance(start_at, str):
start_at = convert_datetime_from_string(start_at)
update = conn.execute("UPDATE battery SET price=? WHERE start_at=?", (price, start_at)).rowcount == 1
conn.commit()
if not update:
logger.error("Can't find line to update in the database")
return update
+21 -4
View File
@@ -1,5 +1,4 @@
from copy import deepcopy
from typing import Tuple
import dash_bootstrap_components as dbc
import dash_table
@@ -14,6 +13,8 @@ from Trip import Trips
from pandas import options as pandas_options
import dash_html_components as html
from libs.elec_price import ElecPrice
def unix_time_millis(dt):
return int(dt.timestamp())
@@ -55,7 +56,7 @@ battery_info = dbc.Alert("No data to show", color="danger")
battery_table = None
def get_figures(trips: Trips, charging: Tuple[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, \
battery_table, consumption_graph_by_temp
lats = []
@@ -116,8 +117,10 @@ def get_figures(trips: Trips, charging: Tuple[dict]):
try:
charge_speed = 3600 * charging_data["kw"].mean() / \
(charging_data["stop_at"] - charging_data["start_at"]).mean().total_seconds()
price_kw = (charging_data["price"] / charging_data["kw"]).mean()
except (TypeError, KeyError): # when there is no data yet:
charge_speed = 0
price_kw = 0
battery_info = dash_table.DataTable(
id='battery_info',
@@ -145,7 +148,18 @@ def get_figures(trips: Trips, charging: Tuple[dict]):
html.Td("Average charge speed:"),
html.Td("{:.3f} kW".format(charge_speed))
]
)
),
html.Tr(
[
html.Td('Average Price:', rowSpan=2),
html.Td("{:.2f} {}/100km".format(price_kw*kw_per_km, ElecPrice.currency)),
]
),
html.Tr(
[
"{:.2f} {}/kW".format(price_kw, ElecPrice.currency),
]
),
])
battery_table = dash_table.DataTable(
@@ -159,8 +173,11 @@ def get_figures(trips: Trips, charging: Tuple[dict]):
{'id': 'co2', 'name': 'CO2', 'type': 'numeric',
'format': deepcopy(nb_format).symbol_suffix(" g/kWh").precision(1)},
{'id': 'kw', 'name': 'consumption', 'type': 'numeric',
'format': deepcopy(nb_format).symbol_suffix(" kWh").precision(3)}],
'format': deepcopy(nb_format).symbol_suffix(" kWh").precision(2)},
{'id': 'price', 'name': 'price', 'type': 'numeric',
'format': deepcopy(nb_format).symbol_suffix(" "+ElecPrice.currency).precision(2)}],
data=charging,
editable=True
)
consumption_by_temp_df = consumption_df[consumption_df["consumption_by_temp"].notnull()]
if len(consumption_by_temp_df) > 0:
+53 -9
View File
@@ -2,33 +2,57 @@ import json
import traceback
from datetime import datetime, timezone
import dash_bootstrap_components as dbc
from dash.dependencies import Output, Input, MATCH
from dash.dependencies import Output, Input, MATCH, State
import dash_core_components as dcc
import dash_html_components as html
import dash_daq as daq
import pandas as pd
from dash.exceptions import PreventUpdate
from MyLogger import logger
from flask import jsonify, request, Response as FlaskResponse
from MyPSACC import MyPSACC
from Trip import Trips
from libs.charging import Charging
from web import figures
from web.app import app, dash_app, myp, chc
import web.db
from web.db import set_chargings_price, get_db, set_db_callback
RESPONSE = "-response"
EMPTY_DIV = "empty-div"
ABRP_SWITCH = 'abrp-switch'
ERROR_DIV = dbc.Alert("No data to show, there is probably no trips recorded yet", color="danger")
trips: Trips
chargings: dict
chargings: list[dict]
min_date = max_date = min_millis = max_millis = step = marks = cached_layout = None
def diff_dashtable(data, data_previous, row_id_name="row_id"):
df, df_previous = pd.DataFrame(data=data), pd.DataFrame(data_previous)
for _df in [df, df_previous]:
assert row_id_name in _df.columns
_df = _df.set_index(row_id_name)
mask = df.ne(df_previous)
df_diff = df[mask].dropna(how="all", axis="columns").dropna(how="all", axis="rows")
changes = []
for idx, row in df_diff.iterrows():
row_id = row.name
row.dropna(inplace=True)
for change in row.iteritems():
changes.append(
{
row_id_name: data[row.name][row_id_name],
"column_name": change[0],
"current_value": change[1],
"previous_value": df_previous.at[row_id, change[0]],
}
)
return changes
@dash_app.callback(Output('trips_map', 'figure'),
Output('consumption_fig', 'figure'),
Output('consumption_fig_by_speed', 'figure'),
@@ -48,7 +72,7 @@ def display_value(value):
for trip in trips:
if mini <= trip.start_at <= maxi:
filtered_trips.append(trip)
filtered_chargings = MyPSACC.get_chargings(mini, maxi)
filtered_chargings = Charging.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, \
@@ -56,6 +80,25 @@ def display_value(value):
figures.battery_table, max_millis, step, marks
@dash_app.callback(
Output(EMPTY_DIV, "children"),
[Input("battery-table", "data_timestamp")],
[
State("battery-table", "data"),
State("battery-table", "data_previous"),
],
)
def capture_diffs(ts, data, data_previous):
if ts is None:
raise PreventUpdate
diff_data = diff_dashtable(data, data_previous, "start_at")
for el in diff_data:
if el['column_name'] == 'price':
if not set_chargings_price(get_db(), el['start_at'], el['current_value']):
logger.error("Can't find line to update in the database")
return ""
@dash_app.callback(Output({'role': ABRP_SWITCH + RESPONSE, 'vin': MATCH}, 'children'),
Input({'role': ABRP_SWITCH, 'vin': MATCH}, 'id'),
Input({'role': ABRP_SWITCH, 'vin': MATCH}, 'value'))
@@ -176,7 +219,7 @@ def update_trips():
trips_by_vin = Trips.get_trips(myp.vehicles_list)
trips = next(iter(trips_by_vin.values())) # todo handle multiple car
assert len(trips) > 0
chargings = MyPSACC.get_chargings()
chargings = Charging.get_chargings()
except (StopIteration, AssertionError):
logger.debug("No trips yet")
return
@@ -258,7 +301,8 @@ def serve_layout():
try:
web.db.callback_fct = update_trips
set_db_callback(update_trips)
Charging.set_default_price()
update_trips()
except (IndexError, TypeError):
logger.debug("Failed to get trips, there is probably not enough data yet %s", traceback.format_exc())