fix electric car record & make backup only if db exist

This commit is contained in:
Florian Bezannier
2022-03-26 12:56:53 +01:00
parent 5ded0917fa
commit 426ba9b71b
10 changed files with 103 additions and 43 deletions
+3 -1
View File
@@ -3,8 +3,10 @@
### 1. Can't get car model
The car model need to be known by the app. To find it, the app use the first 10 character of your VIN.
If the car isn't in the list we need to add it, to do that you need to edit the file here:
### todo
Go to [car_models.yml](https://github.com/flobz/psa_car_controller/blob/develop/psa_car_controller/psacc/resources/car_models.yml)
and click on edit then copy cut and already existent model in the list and edit all properties that are incorrect for your model.
Finally, click on propose change.
### 2. Error during activation {'newversion': '2.0.0', 'newversionurl': 'http://m.inwebo.com/', 'err': 'NOK:FORBIDDEN'}
Your psa account is locked because you makes 20 sms activation. To unlock do this :
+2
View File
@@ -93,6 +93,8 @@ You can modify a price manually in the dashboard. It can be useful if you use pu
## FAQ
If you have a problem, or a question please check if the answer isn't in the [FAQ](FAQ.md).
## Contribute
If you need information to contribute or edit this program go [here](docs/Develop.md).
## Donation
+7 -2
View File
@@ -9,13 +9,14 @@ DIR = os.path.dirname(os.path.realpath(__file__))
if sys.version_info < (3, 7):
raise RuntimeError("This application requires Python 3.7+")
# pylint: disable=wrong-import-position
from psa_car_controller.psacc.application.car_controller import PSACarController
from psa_car_controller import web
# noqa: MC0001
if __name__ == "__main__":
def main():
app = PSACarController()
app.load_app()
args = app.args
@@ -23,3 +24,7 @@ if __name__ == "__main__":
args=["My car info", args.base_path, logger.level < 20, args.listen, int(args.port)], daemon=True)
t1.start()
t1.join()
if __name__ == "__main__":
main()
+1 -1
View File
@@ -305,7 +305,7 @@ def save_otp(obj, filename="otp.bin"):
class RenameUnpickler(pickle.Unpickler):
def find_class(self, module, name):
renamed_module = "psa_car_controller." + module.lower()
renamed_module = "psa_car_controller.psa." + module.lower()
return super().find_class(renamed_module, name)
+2 -2
View File
@@ -70,7 +70,7 @@ class Car:
raise ValueError("ABRP model is not set")
@property
def status(self):
def status(self) -> CarStatus:
return self._status
@status.setter
@@ -78,7 +78,7 @@ class Car:
self._status = value
if self._status is not None and self.status.__class__ != CarStatus:
self._status.__class__ = CarStatus
self._status.correct()
self._status.correct(self.is_electric())
def get_charge_speed(self, diff_elvel, duration_in_sec) -> float:
duration_in_hour = duration_in_sec / 3600
+4 -2
View File
@@ -19,9 +19,9 @@ class CarStatus(Status):
service=None, timed_odometer=None): # noqa: E501
super().__init__(embedded, links, battery, doors_state, energy, environment, ignition, kinetic, last_position,
preconditionning, privacy, safety, service, timed_odometer)
self.correct()
self.correct(False)
def correct(self):
def correct(self, electric_car):
try:
if len(self.last_position.geometry.coordinates) < 2:
raise AttributeError()
@@ -39,6 +39,8 @@ class CarStatus(Status):
if self.timed_odometer is None:
self.timed_odometer = VehicleOdometer()
if electric_car:
self.get_energy("Fuel").level = None
def is_moving(self):
try:
+16 -11
View File
@@ -84,15 +84,20 @@ class Database:
@staticmethod
def init_db(conn):
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,
altitude INTEGER);""")
new_db = True
try:
conn.execute("""CREATE TABLE position (Timestamp DATETIME PRIMARY KEY,
VIN TEXT, longitude REAL,
latitude REAL,
mileage REAL,
level INTEGER,
level_fuel INTEGER,
moving BOOLEAN,
temperature INTEGER,
altitude INTEGER);""")
except sqlite3.OperationalError:
new_db = False
logger.debug("Database already exist")
make_backup = False
conn.execute("CREATE TABLE IF NOT EXISTS battery (start_at DATETIME PRIMARY KEY,stop_at DATETIME,VIN TEXT, "
"start_level INTEGER, end_level INTEGER, co2 INTEGER, kw INTEGER);")
@@ -108,7 +113,7 @@ class Database:
make_backup = True
except sqlite3.OperationalError:
pass
if make_backup:
if not new_db and make_backup:
Database.backup(conn)
conn.execute("DROP TRIGGER IF EXISTS update_trigger")
Database.clean_battery(conn)
@@ -237,7 +242,7 @@ class Database:
conn = Database.get_db()
if conn.execute("SELECT Timestamp from position where Timestamp=?", (date,)).fetchone() is None:
temp = get_temp(latitude, longitude, weather_api)
if level_fuel == 0: # fix fuel level not provided when car is off
if level_fuel and level_fuel == 0: # fix fuel level not provided when car is off
try:
level_fuel = conn.execute(
"SELECT level_fuel FROM position WHERE level_fuel>0 AND VIN=? ORDER BY Timestamp DESC "
+7 -1
View File
@@ -4,6 +4,11 @@ version = "3.0.0"
description = "This is a python program to control a psa car with connected_car v4 api."
authors = ["Florian Bezannier <florian.bezannier@hotmail.fr>"]
license = "GPL-3.0"
homepage = "https://github.com/flobz/psa_car_controller"
repository = "https://github.com/flobz/psa_car_controller"
include = [
"LICENSE",
]
[tool.poetry.dependencies]
python = "^3.7.0"
@@ -37,12 +42,13 @@ prospector = ">=1.3.0"
pre-commit = "^2.17.0"
coverage = "^6.3.2"
deepdiff = "^5.7.0"
selenium = "^4.1.3"
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
[tool.poetry.scripts]
psa-car-controller = 'psa_car_controller.__main__:main'
[tool.autopep8]
max_line_length = 120
+39
View File
@@ -0,0 +1,39 @@
FUEL_CAR_STATUS = {
'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'}
ELECTRIC_CAR_STATUS = {
"lastPosition": {"type": "Feature", "geometry": {"type": "Point", "coordinates": [-1.59008, 47.274, 30]},
"properties": {"updatedAt": "2021-03-29T06:22:51Z", "type": "Aquire", "signalQuality": 9}},
"preconditionning": {"airConditioning": {"updatedAt": "2022-03-26T10:52:11Z", "status": "Disabled"}},
"energy": [{"updatedAt": "2021-09-14T20:39:06Z", "type": "Fuel", "level": 0},
{"updatedAt": "2022-03-26T11:02:54Z", "type": "Electric", "level": 59, "autonomy": 122,
"charging": {"plugged": False, "status": "Disconnected", "remainingTime": "PT0S", "chargingRate": 0,
"chargingMode": "No", "nextDelayedTime": "PT22H31M"}}],
"createdAt": "2022-03-26T11:02:54Z",
"battery": {"voltage": 83.5, "current": 0, "createdAt": "2022-03-26T10:52:11Z"},
"kinetic": {"createdAt": "2021-03-29T06:22:51Z", "moving": True},
"privacy": {"createdAt": "2022-03-26T11:02:53Z", "state": "None"},
"service": {"type": "Electric", "updatedAt": "2022-03-26T11:02:54Z"}, "_links": {"self": {
"href": "https://api.groupe-psa.com/connectedcar/v4/user/vehicles/aa/status"},
"vehicles": {
"href": "https://api.groupe-psa.com/connectedcar/v4/user/vehicles/aa"}},
"timed.odometer": {"createdAt": None, "mileage": 3196.5}, "updatedAt": "2022-03-26T11:02:54Z"}
+22 -23
View File
@@ -3,9 +3,10 @@ import json
import os
import unittest
from datetime import datetime, timedelta
from unittest.mock import MagicMock
from unittest.mock import MagicMock, patch
import reverse_geocode
from dateutil.tz import tzutc
from pytz import UTC
from psa_car_controller import psa
@@ -28,6 +29,7 @@ from psa_car_controller.psacc.repository.config_repository import ConfigReposito
from psa_car_controller.psacc.repository.db import Database
from psa_car_controller.psacc.repository.trips import Trips
from psa_car_controller.psacc.utils.utils import get_temp
from tests.data.car_status import FUEL_CAR_STATUS, ELECTRIC_CAR_STATUS
from tests.utils import DATA_DIR, record_position, latitude, longitude, date0, date1, date2, date3, record_charging, \
vehicule_list, get_new_test_db, get_date, date4
from psa_car_controller.web.figures import get_figures, get_battery_curve_fig, get_altitude_fig
@@ -131,39 +133,36 @@ class TestUnit(unittest.TestCase):
self.assertEqual(charge.price, res.price)
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: psa.connected_car_api.models.status.Status = api._ApiClient__deserialize(res, "Status")
status: psa.connected_car_api.models.status.Status = api._ApiClient__deserialize(FUEL_CAR_STATUS, "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 = PSAClient.load_config(DATA_DIR + "config.json")
myp.record_info(car)
assert geocode_res["country_code"] == "DE"
assert "features" in json.loads(Database.get_recorded_position())
# electric should be first
assert car.status.energy[0].type == 'Electric'
@patch("psa_car_controller.psacc.repository.db.Database.record_position")
def test_electric_record_info(self, mock_db):
api = ApiClient()
status: psa.connected_car_api.models.status.Status = api._ApiClient__deserialize(ELECTRIC_CAR_STATUS, "Status")
get_new_test_db()
car = self.vehicule_list[0]
car.status = status
myp = PSAClient.load_config(DATA_DIR + "config.json")
myp.record_info(car)
db_record_position_arg = mock_db.call_args_list[0][0]
expected_result = (None, 'VR3UHZKX', 3196.5, 47.274, -1.59008, 30,
datetime(2022, 3, 26, 11, 2, 54, tzinfo=tzutc()),
59.0,
None,
True)
self.assertEqual(db_record_position_arg, expected_result)
def test_record_position_charging(self):
get_new_test_db()
config_repository.CONFIG_FILENAME = DATA_DIR + "config.ini"