forked from flobz/psa_car_controller
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmy_psacc.py
225 lines (201 loc) · 9.59 KB
/
my_psacc.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
import json
import threading
from json import JSONEncoder
from hashlib import md5
from time import sleep
from oauth2_client.credentials_manager import ServiceInformation
from urllib3.exceptions import InvalidHeader
import psa_connectedcar as psac
from libs.car import Cars, Car
from libs.charging import Charging
from libs.psa.AccountInformation import AccountInformation
from libs.psa.RemoteClient import RemoteClient
from libs.psa.RemoteCredentials import RemoteCredentials
from libs.psa.oauth import OpenIdCredentialManager, Oauth2PSACCApiConfig, OauthAPIClient
from ecomix import Ecomix
from libs.psa.constants import realm_info, AUTHORIZE_SERVICE
from psa_connectedcar.rest import ApiException
from mylogger import logger
from web.abrp import Abrp
from web.db import Database
SCOPE = ['openid profile']
CARS_FILE = "cars.json"
DEFAULT_CONFIG_FILENAME = "config.json"
class MyPSACC:
def connect(self, user, password):
self.manager.init_with_user_credentials_realm(user, password, self.realm)
# pylint: disable=too-many-arguments
def __init__(self, refresh_token, client_id, client_secret, remote_refresh_token, customer_id, realm, country_code,
proxies=None, weather_api=None, abrp=None, co2_signal_api=None):
self.realm = realm
self.service_information = ServiceInformation(AUTHORIZE_SERVICE,
realm_info[self.realm]['oauth_url'],
client_id,
client_secret,
SCOPE, False)
self.client_id = client_id
self.manager = OpenIdCredentialManager(self.service_information)
self.api_config = Oauth2PSACCApiConfig()
self.api_config.set_refresh_callback(self.manager.refresh_token_now)
self.manager.refresh_token = refresh_token
self.account_info = AccountInformation(client_id, customer_id, realm, country_code)
self.remote_access_token = None
self.vehicles_list = Cars.load_cars(CARS_FILE)
self.customer_id = customer_id
self._config_hash = None
self.api_config.verify_ssl = False
self.api_config.api_key['client_id'] = self.client_id
self.api_config.api_key['x-introspect-realm'] = self.realm
self.remote_token_last_update = None
self._record_enabled = False
self.weather_api = weather_api
self.country_code = country_code
self.info_callback = []
self.info_refresh_rate = 120
if abrp is None:
self.abrp = Abrp()
else:
self.abrp: Abrp = Abrp(**abrp)
self.set_proxies(proxies)
self.config_file = DEFAULT_CONFIG_FILENAME
Ecomix.co2_signal_key = co2_signal_api
self.refresh_thread = None
remote_credentials = RemoteCredentials(remote_refresh_token)
remote_credentials.update_callbacks.append(self.save_config)
self.remote_client = RemoteClient(self.account_info,
self.vehicles_list,
self.manager,
remote_credentials)
def get_app_name(self):
return realm_info[self.realm]['app_name']
def api(self) -> psac.VehiclesApi:
self.api_config.access_token = self.manager.access_token
api_instance = psac.VehiclesApi(OauthAPIClient(self.api_config))
return api_instance
def set_proxies(self, proxies):
if proxies is None:
proxies = dict(http='', https='')
self.api_config.proxy = None
else:
self.api_config.proxy = proxies['http']
self.abrp.proxies = proxies
self.manager.proxies = proxies
def get_vehicle_info(self, vin, cache=False):
res = None
car = self.vehicles_list.get_car_by_vin(vin)
if cache and car.status is not None:
res = car.status
else:
for _ in range(0, 2):
try:
res = self.api().get_vehicle_status(car.vehicle_id, extension=["odometer"])
if res is not None:
car.status = res
if self._record_enabled:
self.record_info(car)
return res
except (ApiException, InvalidHeader) as ex:
logger.error("get_vehicle_info: ApiException: %s", ex, exc_info_debug=True)
car.status = res
return res
def __refresh_vehicle_info(self):
if self.info_refresh_rate is not None:
while True:
try:
logger.debug("refresh_vehicle_info")
for car in self.vehicles_list:
self.get_vehicle_info(car.vin)
for callback in self.info_callback:
callback()
except: # pylint: disable=bare-except
logger.exception("refresh_vehicle_info: ")
sleep(self.info_refresh_rate)
def start_refresh_thread(self):
if self.refresh_thread is None:
self.refresh_thread = threading.Thread(target=self.__refresh_vehicle_info)
self.refresh_thread.setDaemon(True)
self.refresh_thread.start()
def get_vehicles(self):
try:
res = self.api().get_vehicles_by_device()
for vehicle in res.embedded.vehicles:
self.vehicles_list.add(Car(vehicle.vin, vehicle.id, vehicle.brand, vehicle.label))
self.vehicles_list.save_cars()
except (ApiException, InvalidHeader):
logger.exception("get_vehicles:")
return self.vehicles_list
def get_charge_status(self, vin):
data = self.get_vehicle_info(vin)
status = data.get_energy('Electric').charging.status
return status
def save_config(self, name=None, force=False):
if name is None:
name = self.config_file
config_str = json.dumps(self, cls=MyPeugeotEncoder, sort_keys=True, indent=4).encode("utf8")
new_hash = md5(config_str).hexdigest()
if force or self._config_hash != new_hash:
with open(name, "wb") as f:
f.write(config_str)
self._config_hash = new_hash
logger.info("save config change")
@staticmethod
def load_config(name="config.json"):
with open(name, "r", encoding="utf-8") as f:
config_str = f.read()
config = dict(**json.loads(config_str))
if "country_code" not in config:
config["country_code"] = input("What is your country code ? (ex: FR, GB, DE, ES...)\n")
for new_el in ["abrp", "co2_signal_api"]:
if new_el not in config:
config[new_el] = None
psacc = MyPSACC(**config)
psacc.config_file = name
return psacc
def set_record(self, value: bool):
self._record_enabled = value
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_rate = car.status.get_energy('Electric').charging.charging_rate
autonomy = car.status.get_energy('Electric').autonomy
Charging.record_charging(car, charging_status, charge_date, level, latitude, longitude, self.country_code,
charging_mode, charging_rate, autonomy)
logger.debug("charging_status:%s ", charging_status)
except AttributeError:
logger.error("charging status not available from api")
def __iter__(self):
for key, value in self.__dict__.items():
yield key, value
class MyPeugeotEncoder(JSONEncoder):
def default(self, mp: MyPSACC): # pylint: disable=arguments-renamed
mpd = {"proxies": mp.manager.proxies,
"refresh_token": mp.manager.refresh_token,
"client_secret": mp.service_information.client_secret,
"abrp": dict(mp.abrp),
"remote_refresh_token": mp.remote_client.remoteCredentials.refresh_token,
"customer_id": mp.account_info.customer_id,
"client_id": mp.account_info.client_id,
"realm": mp.account_info.realm,
"country_code": mp.account_info.country_code,
"weather_api": mp.weather_api,
"co2_signal_api": Ecomix.co2_signal_key
}
return mpd