mirror of
https://github.com/flobz/psa_car_controller.git
synced 2026-08-26 10:17:18 +00:00
fix electric car record & make backup only if db exist
This commit is contained in:
@@ -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 :
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user