mirror of
https://github.com/flobz/psa_car_controller.git
synced 2026-08-22 01:16:14 +00:00
update api, update battery record
This commit is contained in:
+20
-19
@@ -9,25 +9,28 @@ from MyPSACC import MyPSACC
|
||||
from MyLogger import logger
|
||||
from psa_connectedcar.rest import ApiException
|
||||
|
||||
|
||||
class ChargeControl:
|
||||
periodicity = 120
|
||||
MQTT_TIMEOUT = 60
|
||||
def __init__(self, psacc:MyPSACC, vin, percentage_threshold, stop_hour):
|
||||
|
||||
def __init__(self, psacc: MyPSACC, vin, percentage_threshold, stop_hour):
|
||||
self.vin = vin
|
||||
self.percentage_threshold = percentage_threshold
|
||||
self.set_stop_hour(stop_hour)
|
||||
self.psacc = psacc
|
||||
self.retry_count = 0
|
||||
self.thread:threading.Timer = None
|
||||
self.thread: threading.Timer = None
|
||||
self.always_check = True
|
||||
|
||||
def set_stop_hour(self,stop_hour):
|
||||
def set_stop_hour(self, stop_hour):
|
||||
if stop_hour is None or stop_hour == [0, 0]:
|
||||
self._stop_hour = None
|
||||
self._next_stop_hour = None
|
||||
else:
|
||||
self._stop_hour = stop_hour
|
||||
self._next_stop_hour = datetime.now().replace(hour=stop_hour[0], minute=stop_hour[1], second=0)
|
||||
if self._next_stop_hour < datetime.now():
|
||||
if self._next_stop_hour < datetime.now():
|
||||
self._next_stop_hour += timedelta(days=1)
|
||||
|
||||
def start(self):
|
||||
@@ -38,18 +41,18 @@ class ChargeControl:
|
||||
stop_charge = True
|
||||
self._next_stop_hour += timedelta(days=1)
|
||||
logger.info("it's time to stop the charge")
|
||||
else :
|
||||
else:
|
||||
stop_charge = False
|
||||
|
||||
if self.percentage_threshold != 100 or stop_charge:
|
||||
if self.percentage_threshold != 100 or stop_charge or self.always_check:
|
||||
res = None
|
||||
try:
|
||||
res = self.psacc.get_vehicle_info(self.vin)
|
||||
except ApiException:
|
||||
logger.error(traceback.format_exc())
|
||||
if res is not None:
|
||||
status = res.energy[0]['charging']['status']
|
||||
level = res.energy[0]["level"]
|
||||
status = res.energy[0].charging.status
|
||||
level = res.energy[0].level
|
||||
logger.info(f"charging status of {self.vin} is {status}, battery level: {level}")
|
||||
if status == "InProgress":
|
||||
# force update if the car doesn't send info during 10 minutes
|
||||
@@ -57,17 +60,17 @@ class ChargeControl:
|
||||
if (datetime.utcnow() - last_update).total_seconds() > 60 * 10:
|
||||
self.psacc.wakeup(self.vin)
|
||||
if (level >= self.percentage_threshold and self.retry_count < 2) or stop_charge:
|
||||
self.psacc.charge_now(self.vin,False)
|
||||
self.psacc.charge_now(self.vin, False)
|
||||
self.retry_count += 1
|
||||
sleep(ChargeControl.MQTT_TIMEOUT)
|
||||
res = self.psacc.get_vehicle_info(self.vin)
|
||||
status = res.energy[0]['charging']['status']
|
||||
status = res.energy[0].charging.status
|
||||
if status == "InProgress":
|
||||
logger.warn(f"retry to stop the charge of {self.vin}")
|
||||
self.psacc.charge_now(self.vin, False)
|
||||
self.retry_count += 1
|
||||
if self._next_stop_hour is not None:
|
||||
next_in_second = (self._next_stop_hour- now).total_seconds()
|
||||
next_in_second = (self._next_stop_hour - now).total_seconds()
|
||||
if next_in_second < periodicity:
|
||||
periodicity = next_in_second
|
||||
else:
|
||||
@@ -95,25 +98,25 @@ class ChargeControls:
|
||||
def save_config(self, name="charge_config.json", force=False):
|
||||
chd = {}
|
||||
for key, el in self.list.items():
|
||||
chd[el.vin] = {"percentage_threshold": el.percentage_threshold, "stop_hour": el._stop_hour}
|
||||
chd[el.vin] = {"percentage_threshold": el.percentage_threshold, "stop_hour": el._stop_hour}
|
||||
config_str = json.dumps(chd, sort_keys=True, indent=4).encode('utf-8')
|
||||
new_hash = md5(config_str).hexdigest()
|
||||
if force or self._confighash != new_hash :
|
||||
if force or self._confighash != new_hash:
|
||||
with open(name, "wb") as f:
|
||||
f.write(config_str)
|
||||
self._confighash = new_hash
|
||||
logger.info("save config change")
|
||||
|
||||
def load_config(psacc:MyPSACC, name="charge_config.json"):
|
||||
def load_config(psacc: MyPSACC, name="charge_config.json"):
|
||||
with open(name, "r") as f:
|
||||
str = f.read()
|
||||
chd = json.loads(str)
|
||||
chd = json.loads(str)
|
||||
charge_control_list = ChargeControls()
|
||||
for vin, el in chd.items():
|
||||
charge_control_list.list[vin] = ChargeControl(psacc,vin,**el)
|
||||
charge_control_list.list[vin] = ChargeControl(psacc, vin, **el)
|
||||
return charge_control_list
|
||||
|
||||
def get(self,vin) -> ChargeControl:
|
||||
def get(self, vin) -> ChargeControl:
|
||||
try:
|
||||
return self.list[vin]
|
||||
except KeyError:
|
||||
@@ -122,5 +125,3 @@ class ChargeControls:
|
||||
def start(self):
|
||||
for vin, charge_control in self.list.items():
|
||||
charge_control.start()
|
||||
|
||||
|
||||
|
||||
+57
-26
@@ -16,6 +16,7 @@ from typing import List
|
||||
|
||||
import psa_connectedcar as psac
|
||||
from Trip import Trip
|
||||
from ecomix import Ecomix
|
||||
from psa_connectedcar import ApiClient
|
||||
from psa_connectedcar.rest import ApiException
|
||||
from MyLogger import logger
|
||||
@@ -25,6 +26,8 @@ import sqlite3
|
||||
|
||||
from web.db import get_db
|
||||
|
||||
BATTERY_POWER = 46
|
||||
|
||||
oauhth_url = {"clientsB2CPeugeot": "https://idpcvs.peugeot.com/am/oauth2/access_token",
|
||||
"clientsB2CCitroen": "https://idpcvs.citroen.com/am/oauth2/access_token",
|
||||
"clientsB2CDS": "https://idpcvs.driveds.com/am/oauth2/access_token",
|
||||
@@ -185,7 +188,7 @@ class MyPSACC:
|
||||
if res is None:
|
||||
res = self.api().get_vehicle_status(self.get_vehicle_id_with_vin(vin), extension=["odometer"])
|
||||
if self._record_enabled:
|
||||
self.record_position(vin, res)
|
||||
self.record_info(vin, res)
|
||||
return res
|
||||
|
||||
# monitor doesn't seem to work
|
||||
@@ -311,7 +314,7 @@ class MyPSACC:
|
||||
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.energy[0]['charging']['nextDelayedTime']
|
||||
hour_str = data.energy[0].charging.next_delayed_time
|
||||
hour = re.findall(reg, hour_str)[0]
|
||||
h = int(hour[0])
|
||||
if hour[1] == '':
|
||||
@@ -322,7 +325,7 @@ class MyPSACC:
|
||||
|
||||
def get_charge_status(self, vin):
|
||||
data = self.get_vehicle_info(vin)
|
||||
status = data.energy[0]['charging']['status']
|
||||
status = data.energy[0].charging.status
|
||||
return status
|
||||
|
||||
def veh_charge_request(self, vin, hour, miinute, charge_type):
|
||||
@@ -407,32 +410,50 @@ class MyPSACC:
|
||||
self._record_enabled = value
|
||||
|
||||
@staticmethod
|
||||
def record_position(vin, res: psac.models.status.Status):
|
||||
longitude = res.last_position.geometry.coordinates[0]
|
||||
latitude = res.last_position.geometry.coordinates[1]
|
||||
date = res.last_position.properties.updated_at
|
||||
mileage = res.timed_odometer.mileage
|
||||
level = res.energy[0]["level"]
|
||||
charging_status = res.energy[0]['charging']['status']
|
||||
if mileage == 0: # fix a bug of the api
|
||||
def record_info(vin, status: psac.models.status.Status):
|
||||
longitude = status.last_position.geometry.coordinates[0]
|
||||
latitude = status.last_position.geometry.coordinates[1]
|
||||
date = status.last_position.properties.updated_at
|
||||
mileage = status.timed_odometer.mileage
|
||||
level = status.energy[0].level
|
||||
charging_status = status.energy[0].charging.status
|
||||
conn = get_db()
|
||||
if mileage == 0: # fix a bug of the api
|
||||
logger.error(f"The api return a wrong mileage for {vin} : {mileage}")
|
||||
else:
|
||||
try:
|
||||
conn = get_db()
|
||||
conn.execute("INSERT INTO position(Timestamp,VIN,longitude,latitude,mileage,level) VALUES(?,?,?,?,?,?)",
|
||||
(date, vin, longitude, latitude, mileage, level))
|
||||
conn.commit()
|
||||
logger.info(f"new position recorded for {vin}")
|
||||
except sqlite3.IntegrityError:
|
||||
logger.debug("position already saved")
|
||||
finally:
|
||||
conn.close()
|
||||
#todo handle battery status
|
||||
if charging_status is "InProgress":
|
||||
#create a new line
|
||||
pass
|
||||
elif charging_status is "Stopped" or "Finished":
|
||||
pass
|
||||
# todo handle battery status
|
||||
charge_date = status.energy[0].updated_at
|
||||
if charging_status == "InProgress":
|
||||
try:
|
||||
in_progress = conn.execute("SELECT stop_at FROM battery WHERE VIN=? ORDER BY start_at DESC limit 1", (vin,)).fetchone()[0] is None
|
||||
except TypeError:
|
||||
in_progress = False
|
||||
if not in_progress:
|
||||
res = conn.execute("INSERT INTO battery(start_at,start_level,VIN) VALUES(?,?,?)",
|
||||
(charge_date, level, vin))
|
||||
conn.commit()
|
||||
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", (vin,)).fetchone()
|
||||
in_progress = stop_at is None
|
||||
if in_progress:
|
||||
co2_per_kw = Ecomix.get_co2_per_kw(start_at, stop_at, latitude, longitude)
|
||||
kw = (level-start_level)/100*BATTERY_POWER
|
||||
res = conn.execute("UPDATE battery set stop_at=?, end_level=?, co2=?, kw=? WHERE start_at=? and VIN=?",
|
||||
(charge_date, level, co2_per_kw, kw, start_at, vin))
|
||||
conn.commit()
|
||||
except:
|
||||
logger.debug("Error when saving status " + traceback.format_exc())
|
||||
pass
|
||||
conn.close()
|
||||
|
||||
@staticmethod
|
||||
def get_recorded_position():
|
||||
@@ -458,9 +479,8 @@ class MyPSACC:
|
||||
end = res[1]
|
||||
trips = []
|
||||
tr = Trip()
|
||||
battery_power = 46
|
||||
for x in range(0,len(res)-2):
|
||||
next_el = res[x+2]
|
||||
for x in range(0, len(res) - 2):
|
||||
next_el = res[x + 2]
|
||||
if end["mileage"] - start["mileage"] == 0 or \
|
||||
(end["Timestamp"] - start["Timestamp"]).total_seconds() / 3600 > 3:
|
||||
start = end
|
||||
@@ -476,7 +496,7 @@ class MyPSACC:
|
||||
tr.add_points(end["longitude"], end["latitude"])
|
||||
tr.duration = (end["Timestamp"] - start["Timestamp"]).total_seconds() / 3600
|
||||
tr.speed_average = tr.distance / tr.duration
|
||||
tr.consumption = (start["level"] - end["level"]) / 100 * battery_power # kw
|
||||
tr.consumption = (start["level"] - end["level"]) / 100 * BATTERY_POWER # kw
|
||||
tr.consumption_km = 100 * tr.consumption / tr.distance # kw/100 km
|
||||
logger.debug(
|
||||
f"Trip: {start['Timestamp']} {tr.distance:.1f}km {tr.duration:.2f}h {tr.speed_average:.2f} km/h {tr.consumption:.2f} kw {tr.consumption_km:.2f}kw/100km")
|
||||
@@ -488,6 +508,19 @@ class MyPSACC:
|
||||
end = next_el
|
||||
return trips
|
||||
|
||||
@staticmethod
|
||||
def get_chargings(min=None, max=None):
|
||||
conn = get_db()
|
||||
if min is not None:
|
||||
if max is not None:
|
||||
res = conn.execute("select * from battery WHERE start_at>=? and start_at<=?",(min,max)).fetchall()
|
||||
else:
|
||||
res = conn.execute("select * from battery WHERE start_at>=?",(min,)).fetchall()
|
||||
elif max is not None:
|
||||
res = conn.execute("select * from battery WHERE start_at<=?", (max,)).fetchall()
|
||||
else:
|
||||
res = conn.execute("select * from battery").fetchall()
|
||||
return tuple(map(dict,res))
|
||||
|
||||
class MyPeugeotEncoder(JSONEncoder):
|
||||
def default(self, mp: MyPSACC):
|
||||
@@ -499,5 +532,3 @@ class MyPeugeotEncoder(JSONEncoder):
|
||||
for el in ["client_id", "realm", "remote_refresh_token", "customer_id"]:
|
||||
mpd[el] = data[el]
|
||||
return mpd
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
from datetime import datetime, timedelta
|
||||
from statistics import mean
|
||||
import xml.etree.ElementTree as ET
|
||||
import requests
|
||||
import reverse_geocode
|
||||
|
||||
|
||||
class Ecomix:
|
||||
@staticmethod
|
||||
def get_data_france(start, end):
|
||||
start_str = start.strftime("%d/%m/%Y")
|
||||
end_str = end.strftime("%d/%m/%Y")
|
||||
res = requests.get(
|
||||
f"https://eco2mix.rte-france.com/curves/eco2mixWeb?type=co2&&dateDeb={start_str}&dateFin={end_str}&mode=NORM",
|
||||
headers={
|
||||
"Origin": "https://www.rte-france.com",
|
||||
"Referer": "https://www.rte-france.com/eco2mix/les-emissions-de-co2-par-kwh-produit-en-france",
|
||||
}
|
||||
)
|
||||
|
||||
etree = ET.fromstring(res.text)
|
||||
period_start = (start.hour + int(start.minute / 30)) * 4
|
||||
period_end = (end.hour + int(end.minute / 30)) * 4
|
||||
|
||||
valeurs = etree.iter("valeur")
|
||||
co2_per_kw = []
|
||||
|
||||
valeur = next(valeurs)
|
||||
while int(valeur.attrib["periode"]) != period_start:
|
||||
valeur = next(valeurs)
|
||||
while int(valeur.attrib["periode"]) != period_end:
|
||||
co2_per_kw.append(int(valeur.text))
|
||||
valeur = next(valeurs)
|
||||
|
||||
return mean(co2_per_kw)
|
||||
|
||||
@staticmethod
|
||||
def get_co2_per_kw(start: datetime, end: datetime, latitude, longitude):
|
||||
location = reverse_geocode.search([(latitude, longitude)])[0]
|
||||
country_code = location["country_code"]
|
||||
# todo implement other countries
|
||||
if country_code == 'FR':
|
||||
co2_per_kw = Ecomix.get_data_france(start, end)
|
||||
else:
|
||||
co2_per_kw = None
|
||||
return co2_per_kw
|
||||
|
||||
@@ -55,6 +55,9 @@ from psa_connectedcar.models.e_coaching import ECoaching
|
||||
from psa_connectedcar.models.e_coaching_links import ECoachingLinks
|
||||
from psa_connectedcar.models.e_coaching_scores import ECoachingScores
|
||||
from psa_connectedcar.models.energy import Energy
|
||||
from psa_connectedcar.models.energy_battery import EnergyBattery
|
||||
from psa_connectedcar.models.energy_battery_health import EnergyBatteryHealth
|
||||
from psa_connectedcar.models.energy_charging import EnergyCharging
|
||||
from psa_connectedcar.models.engine import Engine
|
||||
from psa_connectedcar.models.engine_oil import EngineOil
|
||||
from psa_connectedcar.models.environment import Environment
|
||||
|
||||
@@ -46,6 +46,9 @@ from psa_connectedcar.models.e_coaching import ECoaching
|
||||
from psa_connectedcar.models.e_coaching_links import ECoachingLinks
|
||||
from psa_connectedcar.models.e_coaching_scores import ECoachingScores
|
||||
from psa_connectedcar.models.energy import Energy
|
||||
from psa_connectedcar.models.energy_battery import EnergyBattery
|
||||
from psa_connectedcar.models.energy_battery_health import EnergyBatteryHealth
|
||||
from psa_connectedcar.models.energy_charging import EnergyCharging
|
||||
from psa_connectedcar.models.engine import Engine
|
||||
from psa_connectedcar.models.engine_oil import EngineOil
|
||||
from psa_connectedcar.models.environment import Environment
|
||||
|
||||
@@ -31,15 +31,243 @@ class Energy(object):
|
||||
and the value is json key in definition.
|
||||
"""
|
||||
swagger_types = {
|
||||
'updated_at': 'datetime',
|
||||
'autonomy': 'float',
|
||||
'battery': 'EnergyBattery',
|
||||
'charging': 'EnergyCharging',
|
||||
'consumption': 'float',
|
||||
'level': 'float',
|
||||
'residual': 'float',
|
||||
'type': 'str'
|
||||
}
|
||||
|
||||
attribute_map = {
|
||||
'updated_at': 'updatedAt',
|
||||
'autonomy': 'autonomy',
|
||||
'battery': 'battery',
|
||||
'charging': 'charging',
|
||||
'consumption': 'consumption',
|
||||
'level': 'level',
|
||||
'residual': 'residual',
|
||||
'type': 'type'
|
||||
}
|
||||
|
||||
def __init__(self): # noqa: E501
|
||||
def __init__(self, updated_at=None, autonomy=None, battery=None, charging=None, consumption=None, level=None, residual=None, type=None): # noqa: E501
|
||||
"""Energy - a model defined in Swagger""" # noqa: E501
|
||||
|
||||
self._updated_at = None
|
||||
self._autonomy = None
|
||||
self._battery = None
|
||||
self._charging = None
|
||||
self._consumption = None
|
||||
self._level = None
|
||||
self._residual = None
|
||||
self._type = None
|
||||
self.discriminator = None
|
||||
|
||||
if updated_at is not None:
|
||||
self.updated_at = updated_at
|
||||
if autonomy is not None:
|
||||
self.autonomy = autonomy
|
||||
if battery is not None:
|
||||
self.battery = battery
|
||||
if charging is not None:
|
||||
self.charging = charging
|
||||
if consumption is not None:
|
||||
self.consumption = consumption
|
||||
if level is not None:
|
||||
self.level = level
|
||||
if residual is not None:
|
||||
self.residual = residual
|
||||
if type is not None:
|
||||
self.type = type
|
||||
|
||||
@property
|
||||
def updated_at(self):
|
||||
"""Gets the updated_at of this Energy. # noqa: E501
|
||||
|
||||
Date when the resource has been updated. # noqa: E501
|
||||
|
||||
:return: The updated_at of this Energy. # noqa: E501
|
||||
:rtype: datetime
|
||||
"""
|
||||
return self._updated_at
|
||||
|
||||
@updated_at.setter
|
||||
def updated_at(self, updated_at):
|
||||
"""Sets the updated_at of this Energy.
|
||||
|
||||
Date when the resource has been updated. # noqa: E501
|
||||
|
||||
:param updated_at: The updated_at of this Energy. # noqa: E501
|
||||
:type: datetime
|
||||
"""
|
||||
|
||||
self._updated_at = updated_at
|
||||
|
||||
@property
|
||||
def autonomy(self):
|
||||
"""Gets the autonomy of this Energy. # noqa: E501
|
||||
|
||||
Vehicle autonomy for this energy class expressed in KM. # noqa: E501
|
||||
|
||||
:return: The autonomy of this Energy. # noqa: E501
|
||||
:rtype: float
|
||||
"""
|
||||
return self._autonomy
|
||||
|
||||
@autonomy.setter
|
||||
def autonomy(self, autonomy):
|
||||
"""Sets the autonomy of this Energy.
|
||||
|
||||
Vehicle autonomy for this energy class expressed in KM. # noqa: E501
|
||||
|
||||
:param autonomy: The autonomy of this Energy. # noqa: E501
|
||||
:type: float
|
||||
"""
|
||||
|
||||
self._autonomy = autonomy
|
||||
|
||||
@property
|
||||
def battery(self):
|
||||
"""Gets the battery of this Energy. # noqa: E501
|
||||
|
||||
|
||||
:return: The battery of this Energy. # noqa: E501
|
||||
:rtype: EnergyBattery
|
||||
"""
|
||||
return self._battery
|
||||
|
||||
@battery.setter
|
||||
def battery(self, battery):
|
||||
"""Sets the battery of this Energy.
|
||||
|
||||
|
||||
:param battery: The battery of this Energy. # noqa: E501
|
||||
:type: EnergyBattery
|
||||
"""
|
||||
|
||||
self._battery = battery
|
||||
|
||||
@property
|
||||
def charging(self):
|
||||
"""Gets the charging of this Energy. # noqa: E501
|
||||
|
||||
|
||||
:return: The charging of this Energy. # noqa: E501
|
||||
:rtype: EnergyCharging
|
||||
"""
|
||||
return self._charging
|
||||
|
||||
@charging.setter
|
||||
def charging(self, charging):
|
||||
"""Sets the charging of this Energy.
|
||||
|
||||
|
||||
:param charging: The charging of this Energy. # noqa: E501
|
||||
:type: EnergyCharging
|
||||
"""
|
||||
|
||||
self._charging = charging
|
||||
|
||||
@property
|
||||
def consumption(self):
|
||||
"""Gets the consumption of this Energy. # noqa: E501
|
||||
|
||||
Instant consumption for thermic vehicles. # noqa: E501
|
||||
|
||||
:return: The consumption of this Energy. # noqa: E501
|
||||
:rtype: float
|
||||
"""
|
||||
return self._consumption
|
||||
|
||||
@consumption.setter
|
||||
def consumption(self, consumption):
|
||||
"""Sets the consumption of this Energy.
|
||||
|
||||
Instant consumption for thermic vehicles. # noqa: E501
|
||||
|
||||
:param consumption: The consumption of this Energy. # noqa: E501
|
||||
:type: float
|
||||
"""
|
||||
|
||||
self._consumption = consumption
|
||||
|
||||
@property
|
||||
def level(self):
|
||||
"""Gets the level of this Energy. # noqa: E501
|
||||
|
||||
|
||||
:return: The level of this Energy. # noqa: E501
|
||||
:rtype: float
|
||||
"""
|
||||
return self._level
|
||||
|
||||
@level.setter
|
||||
def level(self, level):
|
||||
"""Sets the level of this Energy.
|
||||
|
||||
|
||||
:param level: The level of this Energy. # noqa: E501
|
||||
:type: float
|
||||
"""
|
||||
if level is not None and level > 100: # noqa: E501
|
||||
raise ValueError("Invalid value for `level`, must be a value less than or equal to `100`") # noqa: E501
|
||||
if level is not None and level < 0: # noqa: E501
|
||||
raise ValueError("Invalid value for `level`, must be a value greater than or equal to `0`") # noqa: E501
|
||||
|
||||
self._level = level
|
||||
|
||||
@property
|
||||
def residual(self):
|
||||
"""Gets the residual of this Energy. # noqa: E501
|
||||
|
||||
Residual electric energy avaialble only for electric energy class expressed in KWh. # noqa: E501
|
||||
|
||||
:return: The residual of this Energy. # noqa: E501
|
||||
:rtype: float
|
||||
"""
|
||||
return self._residual
|
||||
|
||||
@residual.setter
|
||||
def residual(self, residual):
|
||||
"""Sets the residual of this Energy.
|
||||
|
||||
Residual electric energy avaialble only for electric energy class expressed in KWh. # noqa: E501
|
||||
|
||||
:param residual: The residual of this Energy. # noqa: E501
|
||||
:type: float
|
||||
"""
|
||||
|
||||
self._residual = residual
|
||||
|
||||
@property
|
||||
def type(self):
|
||||
"""Gets the type of this Energy. # noqa: E501
|
||||
|
||||
|
||||
:return: The type of this Energy. # noqa: E501
|
||||
:rtype: str
|
||||
"""
|
||||
return self._type
|
||||
|
||||
@type.setter
|
||||
def type(self, type):
|
||||
"""Sets the type of this Energy.
|
||||
|
||||
|
||||
:param type: The type of this Energy. # noqa: E501
|
||||
:type: str
|
||||
"""
|
||||
allowed_values = ["Fuel", "Electric"] # noqa: E501
|
||||
if type not in allowed_values:
|
||||
raise ValueError(
|
||||
"Invalid value for `type` ({0}), must be one of {1}" # noqa: E501
|
||||
.format(type, allowed_values)
|
||||
)
|
||||
|
||||
self._type = type
|
||||
|
||||
def to_dict(self):
|
||||
"""Returns the model properties as a dict"""
|
||||
result = {}
|
||||
|
||||
+2
-1
@@ -11,9 +11,10 @@ argparse
|
||||
flask
|
||||
dash_bootstrap_components
|
||||
geojson
|
||||
reverse_geocode
|
||||
|
||||
#swagger req
|
||||
certifi >= 14.05.14
|
||||
six >= 1.10
|
||||
python_dateutil >= 2.5.3
|
||||
urllib3 >= 1.15.1
|
||||
urllib3 >= 1.15.1
|
||||
|
||||
+12
-6
@@ -16,12 +16,15 @@ from web.app import app, dash_app, myp, chc
|
||||
import web.db
|
||||
|
||||
trips = None
|
||||
chargings = None
|
||||
|
||||
|
||||
@dash_app.callback(Output('trips_map', 'figure'),
|
||||
Output('consumption_fig', 'figure'),
|
||||
Output('consumption_fig_by_speed', 'figure'),
|
||||
Output('consumption', 'children'),
|
||||
Output('tab_trips', 'children'),
|
||||
Output('tab_battery', 'children'),
|
||||
Input('date-slider', 'value'))
|
||||
def display_value(value):
|
||||
min = datetime.fromtimestamp(value[0], tz=timezone.utc)
|
||||
@@ -30,7 +33,8 @@ def display_value(value):
|
||||
for trip in trips:
|
||||
if min <= trip.start_at <= max:
|
||||
filtered_trips.append(trip)
|
||||
figures.get_figures(filtered_trips)
|
||||
filtered_chargings = MyPSACC.get_chargings(min,max)
|
||||
figures.get_figures(filtered_trips,filtered_chargings)
|
||||
consumption = "Average consumption: {:.1f} kW/100km".format(float(figures.consumption_df.mean(numeric_only=True)))
|
||||
return figures.trips_map, figures.consumption_fig, figures.consumption_fig_by_speed, consumption, figures.table_fig
|
||||
|
||||
@@ -107,23 +111,25 @@ def after_request(response):
|
||||
|
||||
|
||||
def update_trips():
|
||||
global trips
|
||||
logger.info("update_trips")
|
||||
global trips, chargings
|
||||
logger.info("update_data")
|
||||
try:
|
||||
trips = MyPSACC.get_trips()
|
||||
chargings = MyPSACC.get_chargings()
|
||||
except:
|
||||
logger.error("update_trips: "+traceback.format_exc())
|
||||
logger.error("update_trips: " + traceback.format_exc())
|
||||
|
||||
|
||||
try:
|
||||
web.db.callback_fct = update_trips
|
||||
update_trips()
|
||||
|
||||
min_date = trips[0].start_at
|
||||
max_date = trips[-1].start_at
|
||||
min_millis = figures.unix_time_millis(min_date)
|
||||
max_millis = figures.unix_time_millis(max_date)
|
||||
step = (max_millis - min_millis) / 100
|
||||
figures.get_figures(trips)
|
||||
figures.get_figures(trips, chargings)
|
||||
data_div = html.Div([dcc.RangeSlider(
|
||||
id='date-slider',
|
||||
min=min_millis,
|
||||
@@ -142,6 +148,7 @@ try:
|
||||
dcc.Graph(figure=figures.consumption_fig_by_speed, id="consumption_fig_by_speed")
|
||||
]),
|
||||
dbc.Tab(label="Trips", tab_id="trips", id="tab_trips", children=[figures.table_fig]),
|
||||
dbc.Tab(label="Battery", tab_id="battery", id="tab_battery", children=[figures.battery_info]),
|
||||
dbc.Tab(label="Map", tab_id="map", children=[
|
||||
dcc.Graph(figure=figures.trips_map, id="trips_map", style={"height": '90vh'})]),
|
||||
],
|
||||
@@ -159,4 +166,3 @@ dash_app.layout = dbc.Container(fluid=True, children=[
|
||||
html.H1('My car info'),
|
||||
data_div
|
||||
])
|
||||
|
||||
|
||||
@@ -19,8 +19,8 @@ def get_db():
|
||||
conn.row_factory = sqlite3.Row
|
||||
conn.execute("CREATE TABLE IF NOT EXISTS position (Timestamp DATETIME PRIMARY KEY, VIN TEXT, longitude REAL, "
|
||||
"latitude REAL, mileage REAL, level INTEGER);")
|
||||
conn.execute("CREATE TABLE IF NOT EXISTS battery (start_at DATETIME PRIMARY KEY,stop_at DATETIME, start_level, "
|
||||
"end_level)")
|
||||
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);")
|
||||
conn.create_function("update_trips", 0, update_callback)
|
||||
conn.execute("CREATE TEMP TRIGGER IF NOT EXISTS update_trigger AFTER INSERT ON position BEGIN "
|
||||
"SELECT update_trips(); END;")
|
||||
|
||||
+16
-4
@@ -1,6 +1,7 @@
|
||||
from copy import deepcopy
|
||||
from typing import List
|
||||
|
||||
import dash_bootstrap_components as dbc
|
||||
import dash_table
|
||||
import numpy as np
|
||||
from dash_table.Format import Format, Scheme, Symbol
|
||||
@@ -46,10 +47,11 @@ consumption_fig_by_speed = None
|
||||
table_fig = None
|
||||
pandas_options.display.float_format = '${:.2f}'.format
|
||||
info = ""
|
||||
battery_info = dbc.Alert("No data to show", color="danger")
|
||||
|
||||
|
||||
def get_figures(trips: List[Trip]):
|
||||
global consumption_fig, consumption_df, trips_map, consumption_fig_by_speed, table_fig, info
|
||||
def get_figures(trips: List[Trip], charging: List[dict]):
|
||||
global consumption_fig, consumption_df, trips_map, consumption_fig_by_speed, table_fig, info, battery_info
|
||||
lats = []
|
||||
lons = []
|
||||
names = []
|
||||
@@ -94,5 +96,15 @@ def get_figures(trips: List[Trip]):
|
||||
go.Scatter(mode="markers", x=consum_df_by_speed["speed"], y=consum_df_by_speed["consumption"],
|
||||
name="Trips"))
|
||||
consumption_fig_by_speed.update_layout(xaxis_title="average Speed km/h", yaxis_title="Consumption kWh/100Km")
|
||||
info = "Average consumption: {:.1f} kW/100km".format(
|
||||
float(consumption_df.mean(numeric_only=True)))
|
||||
kw_per_km = float(consumption_df.mean(numeric_only=True))
|
||||
info = "Average consumption: {:.1f} kW/100km".format(kw_per_km)
|
||||
|
||||
# charging
|
||||
charging_data = DataFrame.from_records(charging)
|
||||
co2_per_kw = charging_data["co2"].sum() / charging_data["kw"].sum()
|
||||
co2_per_km = co2_per_kw * kw_per_km / 100
|
||||
charge_speed = 3600 * charging_data["kw"].mean() / \
|
||||
(charging_data["stop_at"] - charging_data["start_at"]).mean().total_seconds()
|
||||
battery_info = "Average gC02/kW: {:.1f}\n" \
|
||||
"Average gC02/km: {:1f}\n" \
|
||||
"Average Charge SPEED {:1f} gC02/kW".format(co2_per_kw, co2_per_km, charge_speed)
|
||||
|
||||
Reference in New Issue
Block a user