def record_info(self, car: Car): mileage = car.status.timed_odometer.mileage level = car.status.get_energy('Electric').level level_fuel = car.status.get_energy('Fuel').level charge_date = car.status.get_energy('Electric').updated_at moving = car.status.kinetic.moving longitude = car.status.last_position.geometry.coordinates[0] latitude = car.status.last_position.geometry.coordinates[1] altitude = car.status.last_position.geometry.coordinates[2] date = car.status.last_position.properties.updated_at if date is None: date = charge_date logger.debug("vin:%s longitude:%s latitude:%s date:%s mileage:%s level:%s charge_date:%s level_fuel:" "%s moving:%s", car.vin, longitude, latitude, date, mileage, level, charge_date, level_fuel, moving) Database.record_position(self.weather_api, car.vin, mileage, latitude, longitude, altitude, date, level, level_fuel, moving) self.abrp.call(car, Database.get_last_temp(car.vin)) try: charging_status = car.status.get_energy('Electric').charging.status charging_mode = car.status.get_energy('Electric').charging.charging_mode Charging.record_charging(car, charging_status, charge_date, level, latitude, longitude, self.country_code, charging_mode) logger.debug("charging_status:%s ", charging_status) except AttributeError: logger.error("charging status not available from api")
def test_db_callback(self): old_dummy_value = dummy_value get_new_test_db() Database.set_db_callback(callback_test) assert old_dummy_value == dummy_value Database.record_position(None, "xx", 11, latitude, longitude - 0.05, None, date0, 40, None, False) assert old_dummy_value != dummy_value
def update_chargings(conn, start_at, stop_at, level, co2_per_kw, consumption_kw, vin): price = Charging.elec_price.get_price(start_at, stop_at, consumption_kw) conn.execute( "UPDATE battery set stop_at=?, end_level=?, co2=?, kw=?, price=? WHERE start_at=? and VIN=?", (stop_at, level, co2_per_kw, consumption_kw, price, start_at, vin)) Database.clean_battery(conn)
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_sdk(self): res = { 'lastPosition': {'type': 'Feature', 'geometry': {'type': 'Point', 'coordinates': [9.65457, 49.96119, 21]}, 'properties': {'updatedAt': '2021-03-29T05:16:10Z', 'heading': 126, 'type': 'Estimated'}}, 'preconditionning': { 'airConditioning': {'updatedAt': '2021-04-01T16:17:01Z', 'status': 'Disabled', 'programs': [ {'enabled': False, 'slot': 1, 'recurrence': 'Daily', 'start': 'PT21H40M', 'occurence': {'day': ['Sat']}}]}}, 'energy': [{'updatedAt': '2021-02-23T22:29:03Z', 'type': 'Fuel', 'level': 0}, {'updatedAt': '2021-04-01T16:17:01Z', 'type': 'Electric', 'level': 70, 'autonomy': 192, 'charging': {'plugged': True, 'status': 'InProgress', 'remainingTime': 'PT0S', 'chargingRate': 20, 'chargingMode': 'Slow', 'nextDelayedTime': 'PT21H30M'}}], 'createdAt': '2021-04-01T16:17:01Z', 'battery': {'voltage': 99, 'current': 0, 'createdAt': '2021-04-01T16:17:01Z'}, 'kinetic': {'createdAt': '2021-03-29T05:16:10Z', 'moving': False}, 'privacy': {'createdAt': '2021-04-01T16:17:01Z', 'state': 'None'}, 'service': {'type': 'Electric', 'updatedAt': '2021-02-23T21:10:29Z'}, '_links': {'self': { 'href': 'https://api.groupe-psa.com/connectedcar/v4/user/vehicles/myid/status'}, 'vehicles': { 'href': 'https://api.groupe-psa.com/connectedcar/v4/user/vehicles/myid'}}, 'timed.odometer': {'createdAt': None, 'mileage': 1107.1}, 'updatedAt': '2021-04-01T16:17:01Z'} api = ApiClient() status: psacc.models.status.Status = api._ApiClient__deserialize(res, "Status") geocode_res = reverse_geocode.search([(status.last_position.geometry.coordinates[:2])[::-1]])[0] assert geocode_res["country_code"] == "DE" get_new_test_db() car = Car("XX", "vid", "Peugeot") car.status = status myp = MyPSACC.load_config(DATA_DIR + "config.json") myp.record_info(car) assert "features" in json.loads(Database.get_recorded_position()) # electric should be first assert car.status.energy[0].type == 'Electric'
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_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_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 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 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 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_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 test_fuel_car(self): TestUnit.get_new_test_db() ElecPrice.CONFIG_FILENAME = DATA_DIR + "config.ini" car = self.vehicule_list[1] Database.record_position(None, car.vin, 11, latitude, longitude, 22, date0, 40, 30, False) Database.record_position(None, car.vin, 20, latitude, longitude, 22, date1, 35, 29, False) Database.record_position(None, car.vin, 30, latitude, longitude, 22, date2, 30, 28, False) trips = Trips.get_trips(self.vehicule_list) res = trips[car.vin].get_trips_as_dict() assert compare_dict(res, [{ 'consumption_km': 5.684210526315789, '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': 0, 'id': 1, 'consumption': 1.08, 'consumption_fuel_km': 10.53 }])
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 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 record_position(): Database.record_position(None, car.vin, 11, latitude, longitude - 0.05, None, date0, 40, None, False) Database.record_position(None, car.vin, 20, latitude, longitude, 32, date1, 35, None, False) Database.record_position(None, car.vin, 30, latitude, longitude, 42, date2, 30, None, False)
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 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
def test_none_mileage(self): get_new_test_db() ElecPrice.CONFIG_FILENAME = DATA_DIR + "config.ini" car = self.vehicule_list[1] Database.record_position(None, car.vin, None, latitude, longitude, 22, get_date(1), 40, 30, False) Database.record_position(None, car.vin, None, latitude, longitude, 22, get_date(2), 35, 29, False) Database.record_position(None, car.vin, None, latitude, longitude, 22, get_date(3), 30, 28, False) start = get_date(4) end = get_date(6) Database.record_position(None, car.vin, 11, latitude, longitude, 22, start, 40, 30, False) Database.record_position(None, car.vin, 20, latitude, longitude, 22, get_date(5), 35, 29, False) Database.record_position(None, car.vin, 30, latitude, longitude, 22, end, 30, 28, False) trips = Trips.get_trips(self.vehicule_list) res = trips[car.vin].get_trips_as_dict() print(res) assert compare_dict(res, [{'consumption_km': 6.947368421052632, 'start_at': start, 'consumption_by_temp': None, 'positions': {'lat': [latitude], 'long': [longitude]}, 'duration': 120.0, 'speed_average': 9.5, 'distance': 19.0, 'mileage': 30.0, 'altitude_diff': 0, 'id': 1, 'consumption': 1.32, 'consumption_fuel_km': 10.53}])
size="xl", ) ]), dbc.Tab(label="Map", tab_id="map", children=[maps]), dbc.Tab(label="Control", tab_id="control", children=dbc.Tabs(id="control-tabs", children=__get_control_tabs())) ], id="tabs", active_tab="summary", persistence=True), html.Div(id=EMPTY_DIV), html.Div(id=EMPTY_DIV + "1") ]) ]) cached_layout = dbc.Container( fluid=True, children=[html.H1('My car info'), data_div]) return cached_layout try: Charging.set_default_price() Database.set_db_callback(update_trips) update_trips() except (IndexError, TypeError): logger.debug("Failed to get trips, there is probably not enough data yet:", exc_info=True) dash_app.layout = serve_layout
def get_recorded_position(): return FlaskResponse(Database.get_recorded_position(), mimetype='application/json')
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))