From 4f017a255d5e99ae3c228661dea28a71f2db0273 Mon Sep 17 00:00:00 2001 From: Florian Bezannier Date: Fri, 14 May 2021 13:21:33 +0200 Subject: [PATCH 01/19] add more info to log --- libs/car_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/car_model.py b/libs/car_model.py index 035e4bb..39c23df 100644 --- a/libs/car_model.py +++ b/libs/car_model.py @@ -31,7 +31,7 @@ class CarModel: if carmodel.match(vin): return carmodel logger.warning("Can't get car model, please report an issue on github with your car model" - " and first ten letter of your VIN") + " and first ten letter of your VIN : %s", vin[:10]) return CarModel("unknown", DEFAULT_BATTERY_POWER, DEFAULT_FUEL_CAPACITY) @staticmethod From 29ebd0135ef34f9dd60b090f49cf3a586705338c Mon Sep 17 00:00:00 2001 From: Florian Bezannier Date: Fri, 14 May 2021 18:19:37 +0200 Subject: [PATCH 02/19] add 2008 car model --- libs/car_model.py | 1 + 1 file changed, 1 insertion(+) diff --git a/libs/car_model.py b/libs/car_model.py index 39c23df..157cf0b 100644 --- a/libs/car_model.py +++ b/libs/car_model.py @@ -57,6 +57,7 @@ carmodels = [ ElecModel("E-C4", 46, "citroen:ec4:21:50", r"VR7BCZKX.*"), # VR7BCZKXCM CarModel("SUV 3008", 10.8, 43), CarModel("308", 0, 56, reg=r"VF3L35GG.*"), + CarModel("2008", 0, 44, reg=r"VR3USHNS.*"), # VR3USHNSKM CarModel("SUV 5008 II", 0, 56, reg=r"VF3MRHNS.*"), # vf3mrhnsum CarModel("C5 Aircross", 10.8, 43), CarModel("DS7 Crossback E-Tense", 13.2, 43, reg="VR1J45GBUK.*") From 8ccb46a00c82adcbbb4562e11539a85764cd8b70 Mon Sep 17 00:00:00 2001 From: Florian Bezannier Date: Fri, 14 May 2021 20:40:05 +0200 Subject: [PATCH 03/19] fix can't get model error --- web/views.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/web/views.py b/web/views.py index 53ac837..01db752 100644 --- a/web/views.py +++ b/web/views.py @@ -10,7 +10,7 @@ import dash_daq as daq from flask import jsonify, request, Response as FlaskResponse import web.utils -from libs.car import Cars, Car +from libs.car import Cars from mylogger import logger from trip import Trips @@ -222,17 +222,17 @@ def update_trips(): conn.close() min_date = None max_date = None - car = myp.vehicles_list[0] # todo handle multiple car try: + assert len(myp.vehicles_list) > 0 + car = myp.vehicles_list[0] # todo handle multiple car + figures.get_figures(car) 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 From d2864eafed8333096c5ad15cde745db272a4ec9e Mon Sep 17 00:00:00 2001 From: Florian Bezannier Date: Sun, 16 May 2021 17:09:48 +0200 Subject: [PATCH 04/19] handle PT0S hour format --- libs/utils.py | 17 +++++++++++++++++ my_psacc.py | 12 ++---------- test/test_unit.py | 7 ++++++- 3 files changed, 25 insertions(+), 11 deletions(-) diff --git a/libs/utils.py b/libs/utils.py index 0058bc3..d6a851b 100644 --- a/libs/utils.py +++ b/libs/utils.py @@ -1,3 +1,4 @@ +import re from functools import wraps from threading import Semaphore, Timer import socket @@ -47,3 +48,19 @@ def rate_limit(limit, every): def is_port_in_use(ip, port): with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: return s.connect_ex((ip, port)) == 0 + + +def parse_hour(hour_str): + reg = r"PT([0-9]{1,2})H([0-9]{1,2})?|PT([0-9]{1,2})S" + hour_minute = re.findall(reg, hour_str)[0] + second = 0 + if hour_minute[0] == '': + hour = 0 + second = hour_minute[2] + else: + hour = int(hour_minute[0]) + if hour_minute[1] == '': + minute = 0 + else: + minute = hour_minute[1] + return hour, minute, second diff --git a/my_psacc.py b/my_psacc.py index 30e05e8..5d18217 100644 --- a/my_psacc.py +++ b/my_psacc.py @@ -1,5 +1,4 @@ import json -import re import threading import uuid from datetime import datetime @@ -22,7 +21,7 @@ from otp.otp import load_otp, new_otp_session, save_otp, ConfigException, Otp from psa_connectedcar.rest import ApiException from mylogger import logger -from libs.utils import rate_limit +from libs.utils import rate_limit, parse_hour from web.abrp import Abrp from web.db import Database @@ -337,17 +336,10 @@ class MyPSACC: return json.dumps(data) def __get_charge_hour(self, vin): - reg = r"PT([0-9]{1,2})H([0-9]{1,2})?" data = self.get_vehicle_info(vin) hour_str = data.get_energy('Electric').charging.next_delayed_time try: - hour_minute = re.findall(reg, hour_str)[0] - hour = int(hour_minute[0]) - if hour_minute[1] == '': - minute = 0 - else: - minute = hour_minute[1] - return hour, minute + return parse_hour(hour_str)[:2] except IndexError: logger.exception("Can't get charge hour: %s", hour_str) return None diff --git a/test/test_unit.py b/test/test_unit.py index 60ab5ad..afbf103 100644 --- a/test/test_unit.py +++ b/test/test_unit.py @@ -16,7 +16,7 @@ from mylogger import my_logger from otp.otp import load_otp, save_otp from charge_control import ChargeControls from trip import Trips -from libs.utils import get_temp +from libs.utils import get_temp, parse_hour from web.db import Database from web.figures import get_figures, get_battery_curve_fig, get_altitude_fig import pytz @@ -250,6 +250,11 @@ class TestUnit(unittest.TestCase): Database.record_position(None, "xx", 11, latitude, longitude - 0.05, None, date0, 40, None, False) assert old_dummy_value != dummy_value + def test_parse_hour(self): + expected_res = [(2, 0, 0), (3, '14', 0), (0, 0, '2')] + assert expected_res == [parse_hour(h) for h in ["PT2H", "PT3H14", "PT2S"]] + + if __name__ == '__main__': my_logger(handler_level=os.environ.get("DEBUG_LEVEL", 20)) unittest.main() From a4c952f32eb10929250d79d37e534125d8062602 Mon Sep 17 00:00:00 2001 From: Florian Bezannier Date: Sun, 16 May 2021 17:19:12 +0200 Subject: [PATCH 05/19] check if a car is compatible with the API --- app_decoder.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app_decoder.py b/app_decoder.py index a776322..825a208 100755 --- a/app_decoder.py +++ b/app_decoder.py @@ -124,6 +124,12 @@ os.chdir(current_dir) psacc.save_config(name="test.json") res = psacc.get_vehicles() +print(f"\nYour vehicles: {res}") + +if len(res) == 0: + print("No vehicle in your account is compatible with this API, you vehicle is probably too old...") + sys.exit(1) + for vehicle in res_dict["vehicles"]: car = psacc.vehicles_list.get_car_by_vin(vehicle["vin"]) if "short_label" in vehicle and car.label == "unknown": @@ -133,7 +139,6 @@ for vehicle in res_dict["vehicles"]: print("Warning: Can't get car model please check cars.json") psacc.vehicles_list.save_cars() -print(f"\nYour vehicles: {res}") # Charge control charge_controls = ChargeControls("charge_config1.json") From e2f213e26085a0097def7e8d7b3f2d2e273f2c74 Mon Sep 17 00:00:00 2001 From: sOckhamSter <4546972+sOckhamSter@users.noreply.github.com> Date: Mon, 17 May 2021 15:20:47 +0100 Subject: [PATCH 06/19] Update init.sh --- docker_files/init.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker_files/init.sh b/docker_files/init.sh index 358050d..408fa99 100644 --- a/docker_files/init.sh +++ b/docker_files/init.sh @@ -20,5 +20,5 @@ then /bin/bash else echo "SHELL_ONLY = FALSE. Running server.py" - python3 /psa_car_controller/server.py -f /config/test.json -c /config/charge_config1.json -p 5000 -l 0.0.0.0 + python3 /psa_car_controller/server.py -f /config/test.json -c /config/charge_config1.json -p 5000 -l 0.0.0.0 -r fi From fd320a64a6db683f2019dfd7ad9287c619bb36d1 Mon Sep 17 00:00:00 2001 From: Florian Bezannier Date: Sun, 16 May 2021 17:19:12 +0200 Subject: [PATCH 07/19] fix discnonect when sending command --- my_psacc.py | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/my_psacc.py b/my_psacc.py index 5d18217..0ef3775 100644 --- a/my_psacc.py +++ b/my_psacc.py @@ -37,6 +37,13 @@ realm_info = { "app_name": "MyVauxhall"} } +MQTT_BRANDCODE = {"AP": "AP", + "AC": "AC", + "DS": "AC", + "VX": "OV", + "OP": "OV" + } + AUTHORIZE_SERVICE = "https://api.mpsa.com/api/connectedcar/v2/oauth/authorize" REMOTE_URL = "https://api.groupe-psa.com/connectedcar/v4/virtualkey/remoteaccess/token?client_id=" SCOPE = ['openid profile'] @@ -255,10 +262,14 @@ class MyPSACC: sleep(60) return None + def __get_mqtt_customer_id(self): + brand_code = self.customer_id[:2] + return MQTT_BRANDCODE[brand_code]+self.customer_id[2:] + # pylint: disable=unused-argument def __on_mqtt_connect(self, client, userdata, result_code, _): logger.info("Connected with result code %s", result_code) - topics = [MQTT_RESP_TOPIC + self.customer_id + "/#"] + topics = [MQTT_RESP_TOPIC + self.__get_mqtt_customer_id() + "/#"] for car in self.vehicles_list: topics.append(MQTT_EVENT_TOPIC + car.vin) for topic in topics: @@ -329,7 +340,7 @@ class MyPSACC: self.refresh_token() date = datetime.utcnow() date_str = date.strftime(PSA_DATE_FORMAT) - data = {"access_token": self.remote_access_token, "customer_id": self.customer_id, + data = {"access_token": self.remote_access_token, "customer_id": self.__get_mqtt_customer_id(), "correlation_id": gen_correlation_id(date), "req_date": date_str, "vin": vin, "req_parameters": req_parameters} @@ -352,7 +363,7 @@ class MyPSACC: def __veh_charge_request(self, vin, hour, minute, charge_type): msg = self.mqtt_request(vin, {"program": {"hour": hour, "minute": minute}, "type": charge_type}) logger.info(msg) - self.mqtt_client.publish(MQTT_REQ_TOPIC + self.customer_id + "/VehCharge", msg) + self.mqtt_client.publish(MQTT_REQ_TOPIC + self.__get_mqtt_customer_id() + "/VehCharge", msg) def change_charge_hour(self, vin, hour, miinute): self.__veh_charge_request(vin, hour, miinute, "delayed") @@ -370,19 +381,19 @@ class MyPSACC: def horn(self, vin, count): msg = self.mqtt_request(vin, {"nb_horn": count, "action": "activate"}) logger.info(msg) - self.mqtt_client.publish(MQTT_REQ_TOPIC + self.customer_id + "/Horn", msg) + self.mqtt_client.publish(MQTT_REQ_TOPIC + self.__get_mqtt_customer_id() + "/Horn", msg) def lights(self, vin, duration: int): msg = self.mqtt_request(vin, {"action": "activate", "duration": duration}) logger.info(msg) - self.mqtt_client.publish(MQTT_REQ_TOPIC + self.customer_id + "/Lights", msg) + self.mqtt_client.publish(MQTT_REQ_TOPIC + self.__get_mqtt_customer_id() + "/Lights", msg) @rate_limit(3, 60 * 20) def wakeup(self, vin): logger.info("ask wakeup to %s", vin) msg = self.mqtt_request(vin, {"action": "state"}) logger.info(msg) - self.mqtt_client.publish(MQTT_REQ_TOPIC + self.customer_id + "/VehCharge/state", msg) + self.mqtt_client.publish(MQTT_REQ_TOPIC + self.__get_mqtt_customer_id() + "/VehCharge/state", msg) return True # get state from server by mqtt @@ -390,7 +401,7 @@ class MyPSACC: logger.info("ask state to %s", vin) msg = self.mqtt_request(vin, {"action": "state"}) logger.info(msg) - self.mqtt_client.publish(MQTT_REQ_TOPIC + self.customer_id + "/VehicleState", msg) + self.mqtt_client.publish(MQTT_REQ_TOPIC + self.__get_mqtt_customer_id() + "/VehicleState", msg) return True def lock_door(self, vin, lock: bool): @@ -401,7 +412,7 @@ class MyPSACC: msg = self.mqtt_request(vin, {"action": value}) logger.info(msg) - self.mqtt_client.publish(MQTT_REQ_TOPIC + self.customer_id + "/Doors", msg) + self.mqtt_client.publish(MQTT_REQ_TOPIC + self.__get_mqtt_customer_id() + "/Doors", msg) return True def preconditioning(self, vin, activate: bool): @@ -422,7 +433,7 @@ class MyPSACC: } msg = self.mqtt_request(vin, {"asap": value, "programs": programs}) logger.info(msg) - self.mqtt_client.publish(MQTT_REQ_TOPIC + self.customer_id + "/ThermalPrecond", msg) + self.mqtt_client.publish(MQTT_REQ_TOPIC + self.__get_mqtt_customer_id() + "/ThermalPrecond", msg) return True def save_config(self, name=None, force=False): @@ -435,7 +446,7 @@ class MyPSACC: f.write(config_str) self._config_hash = new_hash logger.info("save config change") - +# disconnect @staticmethod def load_config(name="config.json"): with open(name, "r") as f: From 06d7c84e20ccf5cbe709664d09d0de4a5c158b68 Mon Sep 17 00:00:00 2001 From: Florian Bezannier Date: Sun, 16 May 2021 17:19:12 +0200 Subject: [PATCH 08/19] fix no car --- app_decoder.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app_decoder.py b/app_decoder.py index 825a208..1056414 100755 --- a/app_decoder.py +++ b/app_decoder.py @@ -132,11 +132,11 @@ if len(res) == 0: for vehicle in res_dict["vehicles"]: car = psacc.vehicles_list.get_car_by_vin(vehicle["vin"]) - if "short_label" in vehicle and car.label == "unknown": + if "short_label" in vehicle and car is not None and car.label == "unknown": car.label = vehicle["short_label"].split(" ")[-1] # remove new, nouvelle, neu word.... car.set_energy_capacity() else: - print("Warning: Can't get car model please check cars.json") + print("Warning: Can't get car model please check cars.json %s", vehicle) psacc.vehicles_list.save_cars() From d32d3bdf5f6fb144d7dd7a5b1333898ed3ba4df2 Mon Sep 17 00:00:00 2001 From: Florian Bezannier Date: Sun, 16 May 2021 21:30:15 +0200 Subject: [PATCH 09/19] fix log format --- my_psacc.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/my_psacc.py b/my_psacc.py index 0ef3775..1e7ed03 100644 --- a/my_psacc.py +++ b/my_psacc.py @@ -155,8 +155,7 @@ class MyPSACC: self.record_info(car) return res except (ApiException, InvalidHeader) as ex: - logger.error("get_vehicle_info: ApiException: %s", ex) - logger.debug(exc_info=True) + logger.error("get_vehicle_info: ApiException: %s", ex, exc_info_debug=True) car.status = res return res From 8e179982acf5df215917a9f5901f4db87af251ef Mon Sep 17 00:00:00 2001 From: Florian Bezannier Date: Thu, 20 May 2021 22:33:53 +0200 Subject: [PATCH 10/19] update faq --- FAQ.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/FAQ.md b/FAQ.md index 09501c3..c851605 100644 --- a/FAQ.md +++ b/FAQ.md @@ -21,3 +21,8 @@ To fix this go to the application directory and execute this command : # if the user is launched by pi user do sudo chown pi: -R . ``` + +### 5. I doesn't receive SMS +The SMS authentication is used to be able to remote control your car. + If your car doesn't have this functionality you should disable remote control when you start psa-car-controller +by using `--remote-disable` argument. From 0a8c1eab4292ed89da8e707088e86fbfac249635 Mon Sep 17 00:00:00 2001 From: Florian Bezannier Date: Mon, 24 May 2021 14:21:23 +0200 Subject: [PATCH 11/19] fix parse hour --- libs/utils.py | 32 +++++++++++++++++--------------- test/test_unit.py | 4 ++-- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/libs/utils.py b/libs/utils.py index d6a851b..2ad60c9 100644 --- a/libs/utils.py +++ b/libs/utils.py @@ -1,9 +1,9 @@ -import re from functools import wraps from threading import Semaphore, Timer import socket import requests +from typing import List from mylogger import logger @@ -50,17 +50,19 @@ def is_port_in_use(ip, port): return s.connect_ex((ip, port)) == 0 -def parse_hour(hour_str): - reg = r"PT([0-9]{1,2})H([0-9]{1,2})?|PT([0-9]{1,2})S" - hour_minute = re.findall(reg, hour_str)[0] - second = 0 - if hour_minute[0] == '': - hour = 0 - second = hour_minute[2] - else: - hour = int(hour_minute[0]) - if hour_minute[1] == '': - minute = 0 - else: - minute = hour_minute[1] - return hour, minute, second +def parse_hour(s): + s = s[2:] + separators = ("H", "M", "S") + res: List[int] = [] + for sep in separators: + if sep in s: + n, s = s.split(sep) + else: + n = 0 + res.append(int(n)) + if s.isnumeric(): + res.append(int(s)) + break + if len(res) == 2: + res.append(0) + return res diff --git a/test/test_unit.py b/test/test_unit.py index afbf103..f9b2118 100644 --- a/test/test_unit.py +++ b/test/test_unit.py @@ -251,8 +251,8 @@ class TestUnit(unittest.TestCase): assert old_dummy_value != dummy_value def test_parse_hour(self): - expected_res = [(2, 0, 0), (3, '14', 0), (0, 0, '2')] - assert expected_res == [parse_hour(h) for h in ["PT2H", "PT3H14", "PT2S"]] + expected_res = [[2, 0, 0], [3, 14, 0], [0, 0, 2], [0, 30, 0]] + assert expected_res == [parse_hour(h) for h in ["PT2H", "PT3H14", "PT2S", "PT30M"]] if __name__ == '__main__': From e8035147230c1697dd4736cb3f60ffc89a96f8f3 Mon Sep 17 00:00:00 2001 From: Florian Bezannier Date: Mon, 24 May 2021 14:24:56 +0200 Subject: [PATCH 12/19] check if variable is set --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 58e17d8..36b3cc5 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -33,4 +33,4 @@ jobs: run: | echo Test coverage run -m unittest || exit 1 - coverage xml -o cobertura.xml && bash <(curl -Ls https://coverage.codacy.com/get.sh) report -r cobertura.xml + [ -n "$CODACY_PROJECT_TOKEN" ] && coverage xml -o cobertura.xml && bash <(curl -Ls https://coverage.codacy.com/get.sh) report -r cobertura.xml From 05403a6eeea7a476304a474eb16cd3fa0ec15573 Mon Sep 17 00:00:00 2001 From: Florian Bezannier Date: Mon, 24 May 2021 14:40:05 +0200 Subject: [PATCH 13/19] add car model: mokka-e, zaphira-e, 5008 2018, DS7 4x4 --- libs/car_model.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/libs/car_model.py b/libs/car_model.py index 157cf0b..a67bf04 100644 --- a/libs/car_model.py +++ b/libs/car_model.py @@ -54,11 +54,16 @@ carmodels = [ ElecModel("e-2008", 46, "peugeot:e2008:20:48", r"VR3UKZKX.*"), ElecModel("e-Spacetourer", 46, "peugeot:etraveler:21:50:citroen", r"VF7VZZKX.*"), ElecModel("corsa-e", 46, "opel:corsae:20:50", r"VXKUHZKX.*"), + # Use corsa in abrp because Mokka isn't available + ElecModel("Mokka-e", 46, "opel:corsae:20:50", r"VXKUKZKX.*"), # VXKUKZKXZM + ElecModel("Zaphira-e", 68, "peugeot:etraveler:21:75:opel", r"VXEVZZKX.*"), # VXEVZZKXZMZ ElecModel("E-C4", 46, "citroen:ec4:21:50", r"VR7BCZKX.*"), # VR7BCZKXCM CarModel("SUV 3008", 10.8, 43), CarModel("308", 0, 56, reg=r"VF3L35GG.*"), CarModel("2008", 0, 44, reg=r"VR3USHNS.*"), # VR3USHNSKM CarModel("SUV 5008 II", 0, 56, reg=r"VF3MRHNS.*"), # vf3mrhnsum + CarModel("SUV 5008 II 2018", 0, 56, reg=r"VF3MRHNY.*"), # VF3MRHNYHH CarModel("C5 Aircross", 10.8, 43), - CarModel("DS7 Crossback E-Tense", 13.2, 43, reg="VR1J45GBUK.*") + CarModel("DS7 Crossback E-Tense", 11.5, 43, reg="VR1J45GBUK.*"), + CarModel("DS7 Crossback E-Tense 300 4x4", 11.5, 43, reg=" VR1J45GBUL.*") ] From 072ea2bf20f810149a591fd3c3e9fce97ba52a65 Mon Sep 17 00:00:00 2001 From: Florian Hinrichsen Date: Wed, 26 May 2021 10:21:29 +0200 Subject: [PATCH 14/19] Update car_model.py Add Peugeot 508 II SW Hybrid --- libs/car_model.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libs/car_model.py b/libs/car_model.py index a67bf04..a7e5029 100644 --- a/libs/car_model.py +++ b/libs/car_model.py @@ -65,5 +65,6 @@ carmodels = [ CarModel("SUV 5008 II 2018", 0, 56, reg=r"VF3MRHNY.*"), # VF3MRHNYHH CarModel("C5 Aircross", 10.8, 43), CarModel("DS7 Crossback E-Tense", 11.5, 43, reg="VR1J45GBUK.*"), - CarModel("DS7 Crossback E-Tense 300 4x4", 11.5, 43, reg=" VR1J45GBUL.*") + CarModel("DS7 Crossback E-Tense 300 4x4", 11.5, 43, reg=" VR1J45GBUL.*"), + CarModel("508 SW Hybrid", 11.5, 45, reg=r"VR3F4DGZ.*") # VR3F4DGZTL ] From ab1c1f6d0cea37a2ecaface8238df2fbd785e12a Mon Sep 17 00:00:00 2001 From: sOckhamSter <4546972+sOckhamSter@users.noreply.github.com> Date: Fri, 28 May 2021 11:50:53 +0100 Subject: [PATCH 15/19] Update Docker.md --- docs/Docker.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Docker.md b/docs/Docker.md index fce3ef2..b457bce 100644 --- a/docs/Docker.md +++ b/docs/Docker.md @@ -33,9 +33,9 @@ python3 /psa_car_controller/app_decoder.py /config/com.psa.mym.myvauxhall_1.27.1 ``` Start server.py for a first run: ``` -python3 /psa_car_controller/server.py -f /config/test.json -c /config/charge_config1.json -p 5000 -l 0.0.0.0 -r -b /config +python3 /psa_car_controller/server.py -f /config/test.json -c /config/charge_config1.json -p 5000 -l 0.0.0.0 -r ``` -You will be prompted to enter a code, sent you you by SMS to your registered mobile number, and the PIN for your account. Sometimes it asks twice (???), but once accepted the service should run. Allow it to run for about a minute before pressing ctrl+c to quit. You should end up back at the bash prompt within the container. +You will be prompted to enter a code, sent by SMS to your registered mobile number, and the PIN for your account. Sometimes it asks twice (???), but once accepted the service should run. Allow it to run for about a minute before pressing ctrl+c to quit. You should end up back at the bash prompt within the container. The final task is to update the container's configuration file to tell it to run the service automatically on next boot. This is done by editing /config/dockerconfig.conf and changing the SHELL_ONLY parameter to FALSE. Alternatively, paste the below command into your shell one by one. These will update the dockerconfig.conf file, exit the container's bash shell, stop the container, then restart the container. From 0b47db1538a923ac7ad44ae32ac09574f096ec2b Mon Sep 17 00:00:00 2001 From: Florian Bezannier Date: Thu, 20 May 2021 23:00:44 +0200 Subject: [PATCH 16/19] avoid error when no error reason --- my_psacc.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/my_psacc.py b/my_psacc.py index 1e7ed03..6c04079 100644 --- a/my_psacc.py +++ b/my_psacc.py @@ -295,10 +295,8 @@ class MyPSACC: elif data["return_code"] == "400": self.refresh_remote_token(force=True) logger.error("retry last request, token was expired") - elif data["return_code"] == "300": - logger.error('%s', data["return_code"]) elif data["return_code"] != "0": - logger.error('%s : %s', data["return_code"], data["reason"]) + logger.error('%s : %s', data["return_code"], data.get("reason", "?")) if msg.topic.endswith("/VehicleState"): charge_info = data["resp_data"]["charging_state"] self.precond_programs[data["vin"]] = data["resp_data"]["precond_state"]["programs"] From c946a2fb1b8758594ddbeec67ccb493c734e17e5 Mon Sep 17 00:00:00 2001 From: Florian Bezannier Date: Fri, 4 Jun 2021 11:03:20 +0200 Subject: [PATCH 17/19] remove getstate mqtt req, because it's not available anymore --- my_psacc.py | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/my_psacc.py b/my_psacc.py index 6c04079..6657af4 100644 --- a/my_psacc.py +++ b/my_psacc.py @@ -297,11 +297,9 @@ class MyPSACC: logger.error("retry last request, token was expired") elif data["return_code"] != "0": logger.error('%s : %s', data["return_code"], data.get("reason", "?")) - if msg.topic.endswith("/VehicleState"): - charge_info = data["resp_data"]["charging_state"] - self.precond_programs[data["vin"]] = data["resp_data"]["precond_state"]["programs"] elif msg.topic.startswith(MQTT_EVENT_TOPIC): charge_info = data["charging_state"] + self.precond_programs[data["vin"]] = data["precond_state"]["programs"] if charge_info is not None and charge_info['remaining_time'] != 0 and charge_info['rate'] == 0: # fix a psa server bug where charge beginning without status api being properly updated logger.warning("charge begin but API isn't updated") @@ -328,7 +326,7 @@ class MyPSACC: def __keep_mqtt(self): # avoid token expiration timeout = 3600 * 24 # 1 day if len(self.vehicles_list) > 0: - self.get_state(self.vehicles_list[0].vin) + self.wakeup(self.vehicles_list[0].vin) t = threading.Timer(timeout, self.__keep_mqtt) t.setDaemon(True) t.start() @@ -393,14 +391,6 @@ class MyPSACC: self.mqtt_client.publish(MQTT_REQ_TOPIC + self.__get_mqtt_customer_id() + "/VehCharge/state", msg) return True - # get state from server by mqtt - def get_state(self, vin): - logger.info("ask state to %s", vin) - msg = self.mqtt_request(vin, {"action": "state"}) - logger.info(msg) - self.mqtt_client.publish(MQTT_REQ_TOPIC + self.__get_mqtt_customer_id() + "/VehicleState", msg) - return True - def lock_door(self, vin, lock: bool): if lock: value = "lock" @@ -417,8 +407,6 @@ class MyPSACC: value = "activate" else: value = "deactivate" - self.get_state(vin) - sleep(2) # wait for rep if vin in self.precond_programs: programs = self.precond_programs[vin] else: From 9f9435a06579e6feee95674a20723d8dfeccb06b Mon Sep 17 00:00:00 2001 From: Florian BEZANNIER <48728684+flobz@users.noreply.github.com> Date: Mon, 14 Jun 2021 09:48:01 +0200 Subject: [PATCH 18/19] Update README.md document charge hour --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 5e927b5..2232636 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,9 @@ For information on configuring the psa_car_controller Docker container [see this 2.7 Start/Stop preconditioning http://localhost:5000/preconditioning/YOURVIN/1 or 0 + + 2.8 Change charge hour (for example: set it to 22h30) + http://127.0.0.1:5000/charge_hour?vin=YOURVIN&hour=22&min=30 ## III. Use the dashboard From 14e639ddb28650d700b32f17d81da60d2bfb2112 Mon Sep 17 00:00:00 2001 From: Florian Bezannier Date: Mon, 14 Jun 2021 17:28:21 +0200 Subject: [PATCH 19/19] fix compare naive vs aware date --- ecomix.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ecomix.py b/ecomix.py index 1ff4008..f211b12 100644 --- a/ecomix.py +++ b/ecomix.py @@ -5,6 +5,7 @@ import numbers import requests import reverse_geocode +from pytz import UTC from mylogger import logger @@ -51,12 +52,13 @@ class Ecomix: def get_data_from_co2_signal(latitude, longitude, country_code_default): if Ecomix.co2_signal_key is not None: try: + now = datetime.utcnow().replace(tzinfo=UTC) country_code = Ecomix.get_country(latitude, longitude, country_code_default) assert country_code is not None if country_code not in Ecomix._cache: Ecomix._cache[country_code] = [] elif len(Ecomix._cache[country_code]) > 0 and \ - (datetime.now() - Ecomix._cache[country_code][-1][0]).total_seconds() < CO2_SIGNAL_REQ_INTERVAL: + (now - Ecomix._cache[country_code][-1][0]).total_seconds() < CO2_SIGNAL_REQ_INTERVAL: return False res = requests.get(CO2_SIGNAL_URL + "/v1/latest", headers={"auth-token": Ecomix.co2_signal_key}, @@ -64,7 +66,7 @@ class Ecomix: data = res.json() value = data["data"]["carbonIntensity"] assert isinstance(value, numbers.Number) - Ecomix._cache[country_code].append([datetime.now(), value]) + Ecomix._cache[country_code].append([now, value]) return data["status"] == "ok" except (AssertionError, NameError, KeyError): logger.debug("ecomix:", exc_info=True)