def get_new_test_db(): try: os.remove(DATA_DIR + "tmp.db") except FileNotFoundError: pass Database.DEFAULT_DB_FILE = DB_DIR Database.db_initialized = False conn = Database.get_db() return conn
def get_altitude_fig(trip: Trip): conn = Database.get_db() res = list(map(list, conn.execute("SELECT mileage, altitude FROM position WHERE Timestamp>=? and Timestamp<=?;", (trip.start_at, trip.end_at)).fetchall())) start_mileage = res[0][0] 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") conn.close() return html.Div(Graph(figure=fig))
def capture_diffs_in_battery_table(timestamp, data, data_previous): # pylint: disable=unused-variable if timestamp is None: raise PreventUpdate diff_data = diff_dashtable(data, data_previous, "start_at") for changed_line in diff_data: if changed_line['column_name'] == 'price': conn = Database.get_db() if not Database.set_chargings_price( conn, changed_line['start_at'], changed_line['current_value']): logger.error("Can't find line to update in the database") conn.close() return ""
def set_default_price(): if Charging.elec_price.is_enable(): conn = Database.get_db() charge_list = list( map( dict, conn.execute("SELECT * FROM battery WHERE price IS NULL"). fetchall())) for charge in charge_list: charge["price"] = Charging.elec_price.get_price( charge["start_at"], charge["stop_at"], charge["kw"]) Database.set_chargings_price(conn, charge["start_at"], charge["price"]) conn.close()
def test_battery_curve(self): from libs.car import Car from libs.charging import Charging try: os.remove("tmp.db") except: pass Database.DEFAULT_DB_FILE = "tmp.db" conn = Database.get_db() list(map(dict, conn.execute('PRAGMA database_list').fetchall())) vin = "VR3UHZKXZL" car = Car(vin, "id", "Peugeot") Charging.record_charging(car, "InProgress", date0, 50, latitude, longitude, "FR", "slow") Charging.record_charging(car, "InProgress", date1, 75, latitude, longitude, "FR", "slow") Charging.record_charging(car, "InProgress", date2, 85, latitude, longitude, "FR", "slow") Charging.record_charging(car, "InProgress", date3, 90, latitude, longitude, "FR", "slow") res = Database.get_battery_curve(Database.get_db(), date0, vin) assert len(res) == 3
def record_charging( car, charging_status, charge_date: datetime, level, latitude, # pylint: disable=too-many-locals longitude, country_code, charging_mode, charging_rate, autonomy): conn = Database.get_db() charge_date = charge_date.replace(microsecond=0) if charging_status == "InProgress": stop_at, start_at = conn.execute( "SELECT stop_at, start_at FROM battery WHERE VIN=? ORDER BY start_at " "DESC limit 1", (car.vin, )).fetchone() or [False, None] try: conn.execute( "INSERT INTO battery_curve(start_at,VIN,date,level,rate,autonomy) VALUES(?,?,?,?,?,?)", (start_at, car.vin, charge_date, level, charging_rate, autonomy)) except IntegrityError: logger.debug("level already stored") if stop_at is not None: conn.execute( "INSERT INTO battery(start_at,start_level,charging_mode,VIN) VALUES(?,?,?,?)", (charge_date, level, charging_mode, car.vin)) Ecomix.get_data_from_co2_signal(latitude, longitude, country_code) else: try: start_at, stop_at, start_level = conn.execute( "SELECT start_at, stop_at, start_level from battery WHERE VIN=? ORDER BY start_at DESC limit 1", (car.vin, )).fetchone() in_progress = stop_at is None except TypeError: logger.debug("battery table is probably empty :", exc_info=True) in_progress = False if in_progress: co2_per_kw = Ecomix.get_co2_per_kw(start_at, charge_date, latitude, longitude, country_code) consumption_kw = (level - start_level) / 100 * car.battery_power Charging.update_chargings(conn, start_at, charge_date, level, co2_per_kw, consumption_kw, car.vin) conn.commit() conn.close()
def update_trips(): global trips, chargings, cached_layout, min_date, max_date, min_millis, max_millis, step, marks logger.info("update_data") conn = Database.get_db(update_callback=False) Database.add_altitude_to_db(conn) conn.close() min_date = None max_date = None if CONFIG.is_good: car = CONFIG.myp.vehicles_list[0] # todo handle multiple car try: trips_by_vin = Trips.get_trips(Cars([car])) trips = trips_by_vin[car.vin] assert len(trips) > 0 min_date = trips[0].start_at max_date = trips[-1].start_at figures.get_figures(trips[0].car) except (AssertionError, KeyError): logger.debug("No trips yet") figures.get_figures(Car("vin", "vid", "brand")) try: chargings = Charging.get_chargings() assert len(chargings) > 0 if min_date: min_date = min(min_date, chargings[0]["start_at"]) max_date = max(max_date, chargings[-1]["start_at"]) else: min_date = chargings[0]["start_at"] max_date = chargings[-1]["start_at"] except AssertionError: logger.debug("No chargings yet") if min_date is None: return # update for slider try: logger.debug("min_date:%s - max_date:%s", min_date, max_date) min_millis = web.utils.unix_time_millis(min_date) max_millis = web.utils.unix_time_millis(max_date) step = (max_millis - min_millis) / 100 marks = web.utils.get_marks_from_start_end(min_date, max_date) cached_layout = None # force regenerate layout figures.get_figures(car) except (ValueError, IndexError): logger.error("update_trips (slider): %s", exc_info=True) except AttributeError: logger.debug("position table is probably empty :", exc_info=True) return
def get_chargings(mini=None, maxi=None) -> List[dict]: conn = Database.get_db() if mini is not None: if maxi is not None: res = conn.execute( "select * from battery WHERE start_at>=? and start_at<=?", (mini, maxi)).fetchall() else: res = conn.execute("select * from battery WHERE start_at>=?", (mini, )).fetchall() elif maxi is not None: res = conn.execute("select * from battery WHERE start_at<=?", (maxi, )).fetchall() else: res = conn.execute("select * from battery").fetchall() conn.close() return list(map(dict, res))
def get_battery_curve_fig(row: dict, car: Car): start_date = dash_date_to_datetime(row["start_at"]) stop_at = dash_date_to_datetime(row["stop_at"]) conn = Database.get_db() res = Database.get_battery_curve(conn, start_date, stop_at, car.vin) conn.close() battery_curves = [] if len(res) > 0: battery_capacity = res[-1]["level"] * car.battery_power / 100 km_by_kw = 0.8 * res[-1]["autonomy"] / battery_capacity start = 0 speeds = [] def speed_in_kw_from_km(row): try: speed = row["rate"] / km_by_kw if speed > 0: speeds.append(speed) except (KeyError, TypeError): pass for end in range(1, len(res)): start_level = res[start]["level"] end_level = res[end]["level"] diff_level = end_level - start_level diff_sec = (res[end]["date"] - res[start]["date"]).total_seconds() speed_in_kw_from_km(res[end - 1]) if diff_sec > 0 and diff_level > 3: speed_in_kw_from_km(res[end]) speed = car.get_charge_speed(diff_level, diff_sec) if len(speeds) > 0: speed = mean([*speeds, speed]) speed = round(speed * 2) / 2 battery_curves.append({"level": start_level, "speed": speed}) start = end speeds = [] battery_curves.append({"level": row["end_level"], "speed": 0}) else: speed = car.get_charge_speed(row["end_level"] - row["start_level"], (stop_at - start_date).total_seconds()) battery_curves.append({"level": row["start_level"], "speed": speed}) battery_curves.append({"level": row["end_level"], "speed": speed}) fig = px.line(battery_curves, x="level", y="speed") fig.update_layout(xaxis_title="Battery %", yaxis_title="Charging speed in kW") return html.Div(Graph(figure=fig))
def test_record_position_charging(self): get_new_test_db() ElecPrice.CONFIG_FILENAME = DATA_DIR + "config.ini" car = self.vehicule_list[0] record_position() Database.add_altitude_to_db(Database.get_db()) data = json.loads(Database.get_recorded_position()) assert data["features"][1]["geometry"]["coordinates"] == [float(longitude), float(latitude)] trips = Trips.get_trips(self.vehicule_list)[car.vin] trip = trips[0] map(trip.add_temperature, [10, 13, 15]) res = trip.get_info() assert compare_dict(res, {'consumption_km': 24.21052631578947, 'start_at': date0, 'consumption_by_temp': None, 'positions': {'lat': [latitude], 'long': [longitude]}, 'duration': 40.0, 'speed_average': 28.5, 'distance': 19.0, 'mileage': 30.0, 'altitude_diff': 2, 'id': 1, 'consumption': 4.6}) Charging.elec_price = ElecPrice.read_config() start_level = 40 end_level = 85 Charging.record_charging(car, "InProgress", date0, start_level, latitude, longitude, None, "slow", 20, 60) Charging.record_charging(car, "InProgress", date1, 70, latitude, longitude, "FR", "slow", 20, 60) Charging.record_charging(car, "InProgress", date1, 70, latitude, longitude, "FR", "slow", 20, 60) Charging.record_charging(car, "InProgress", date2, 80, latitude, longitude, "FR", "slow", 20, 60) Charging.record_charging(car, "Stopped", date3, end_level, latitude, longitude, "FR", "slow", 20, 60) chargings = Charging.get_chargings() co2 = chargings[0]["co2"] assert isinstance(co2, float) assert compare_dict(chargings, [{'start_at': date0, 'stop_at': date3, 'VIN': 'VR3UHZKX', 'start_level': 40, 'end_level': 85, 'co2': co2, 'kw': 20.7, 'price': 3.84, 'charging_mode': 'slow'}]) print() assert get_figures(car) row = {"start_at": date0.strftime('%Y-%m-%dT%H:%M:%S.000Z'), "stop_at": date3.strftime('%Y-%m-%dT%H:%M:%S.000Z'), "start_level": start_level, "end_level": end_level} assert get_battery_curve_fig(row, car) is not None assert get_altitude_fig(trip) is not None
def get_battery_curve_fig(row: dict, car: Car): start_date = dash_date_to_datetime(row["start_at"]) stop_at = dash_date_to_datetime(row["stop_at"]) conn = Database.get_db() res = Database.get_battery_curve(conn, start_date, car.vin) conn.close() res.insert(0, {"level": row["start_level"], "date": start_date}) res.append({"level": row["end_level"], "date": stop_at}) battery_curves = [] speed = 0 for x in range(1, len(res)): start_level = res[x - 1]["level"] end_level = res[x]["level"] speed = car.get_charge_speed(start_level, end_level, (res[x]["date"] - res[x - 1]["date"]).total_seconds()) battery_curves.append({"level": start_level, "speed": speed}) battery_curves.append({"level": row["end_level"], "speed": speed}) fig = px.line(battery_curves, x="level", y="speed") fig.update_layout(xaxis_title="Battery %", yaxis_title="Charging speed in kW") return html.Div(Graph(figure=fig))
def capture_diffs_in_battery_table(timestamp, data, data_previous): if timestamp is None: raise PreventUpdate diff_data = diff_dashtable(data, data_previous, "start_at") for changed_line in diff_data: if changed_line['column_name'] == 'price': conn = Database.get_db() if not Database.set_chargings_price( conn, dash_date_to_datetime(changed_line['start_at']), changed_line['current_value']): logger.error( "Can't find line to update in the database") else: logger.debug("update price %s of %s", changed_line['current_value'], changed_line['start_at']) conn.close() return "" # don't need to update dashboard
def update_trips(): global trips, chargings, cached_layout, min_date, max_date, min_millis, max_millis, step, marks logger.info("update_data") conn = Database.get_db(update_callback=False) Database.add_altitude_to_db(conn) conn.close() min_date = None max_date = None try: trips_by_vin = Trips.get_trips(myp.vehicles_list) trips = next(iter(trips_by_vin.values())) # todo handle multiple car assert len(trips) > 0 min_date = trips[0].start_at max_date = trips[-1].start_at except (StopIteration, AssertionError): logger.debug("No trips yet") try: chargings = Charging.get_chargings() assert len(chargings) > 0 if min_date: min_date = min(min_date, chargings[0]["start_at"]) max_date = max(max_date, chargings[-1]["start_at"]) else: min_date = chargings[0]["start_at"] max_date = chargings[-1]["start_at"] except AssertionError: logger.debug("No chargings yet") if min_date is None: return # update for slider try: logger.debug("min_date:%s - max_date:%s", min_date, max_date) 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) cached_layout = None # force regenerate layout except (ValueError, IndexError): logger.error("update_trips (slider): %s", exc_info=True) except AttributeError: logger.debug("position table is probably empty :", exc_info=True) return
def get_trips(vehicles_list: Cars) -> Dict[str, "Trips"]: # noqa: MC0001 # pylint: disable=too-many-locals,too-many-statements,too-many-nested-blocks,too-many-branches conn = Database.get_db() vehicles = conn.execute( "SELECT DISTINCT vin FROM position;").fetchall() trips_by_vin = {} for vin in vehicles: trips = Trips() vin = vin[0] res = conn.execute( 'SELECT Timestamp, VIN, longitude, latitude, mileage, level, moving, temperature,' ' level_fuel, altitude FROM position WHERE VIN=? ORDER BY Timestamp', (vin, )).fetchall() if len(res) > 1: car = vehicles_list.get_car_by_vin(vin) assert car is not None trip_parser = TripParser(car) start = res[0] end = res[1] trip = Trip() # for debugging use this line res = list(map(dict,res)) for x in range(0, len(res) - 2): if logger.isEnabledFor( logging.DEBUG ): # reduce execution time if debug disabled logger.debugv("%s mileage:%.1f level:%s level_fuel:%s", res[x]['Timestamp'], res[x]['mileage'], res[x]['level'], res[x]['level_fuel']) next_el = res[x + 2] distance = 0 try: distance = end["mileage"] - start["mileage"] except TypeError: logger.debug("Bad mileage value in DB") duration = (end["Timestamp"] - start["Timestamp"]).total_seconds() / 3600 try: speed_average = distance / duration except ZeroDivisionError: speed_average = 0 if TripParser.is_low_speed( speed_average, duration) or trip_parser.is_refuel( start, end, distance): start = end trip = Trip() logger.debugv( "restart trip at {0[Timestamp]} mileage:{0[mileage]:.1f} level:{0[level]}" " level_fuel:{0[level_fuel]}", start, style='{') else: distance = next_el["mileage"] - end["mileage"] # km duration = (next_el["Timestamp"] - end["Timestamp"]).total_seconds() / 3600 try: speed_average = distance / duration except ZeroDivisionError: speed_average = 0 end_trip = False if trip_parser.is_refuel(end, next_el, distance) or \ TripParser.is_low_speed(speed_average, duration): end_trip = True elif duration > 2: end_trip = True logger.debugv("too much time detected") elif x == len(res) - 3: # last record detected # think if add point is needed end = next_el end_trip = True logger.debugv("last position found") if end_trip: logger.debugv( "stop trip at {0[Timestamp]} mileage:{0[mileage]:.1f} level:{0[level]}" " level_fuel:{0[level_fuel]}", end, style='{') trip.distance = end["mileage"] - start[ "mileage"] # km if trip.distance > 0: trip.start_at = start["Timestamp"] trip.end_at = end["Timestamp"] trip.add_points(end["latitude"], end["longitude"]) if end["temperature"] is not None and start[ "temperature"] is not None: trip.add_temperature(end["temperature"]) trip.duration = ( end["Timestamp"] - start["Timestamp"]).total_seconds() / 3600 trip.speed_average = trip.distance / trip.duration diff_level, diff_level_fuel = trip_parser.get_level_consumption( start, end) trip.set_altitude_diff(start["altitude"], end["altitude"]) trip.car = car if diff_level != 0: trip.set_consumption(diff_level) # kw if diff_level_fuel != 0: trip.set_fuel_consumption(diff_level_fuel) trip.mileage = end["mileage"] logger.debugv( "Trip: {0.start_at} -> {0.end_at} {0.distance:.1f}km {0.duration:.2f}h " "{0.speed_average:.0f}km/h {0.consumption:.2f}kWh " "{0.consumption_km:.2f}kWh/100km {0.consumption_fuel:.2f}L " "{0.consumption_fuel_km:.2f}L/100km {0.mileage:.1f}km", trip, style="{") # filter bad value trips.check_and_append(trip) start = next_el trip = Trip() else: trip.add_points(end["latitude"], end["longitude"]) end = next_el trips_by_vin[vin] = trips conn.close() return trips_by_vin
def get_chargings() -> List[dict]: conn = Database.get_db() res = conn.execute( "select * from battery ORDER BY start_at").fetchall() conn.close() return list(map(dict, res))
def test_battery_curve(self): get_new_test_db() record_charging() res = Database.get_battery_curve(Database.get_db(), date0, date4, self.vehicule_list[0].vin) assert len(res) == 3