Skip to content
Merged
2 changes: 2 additions & 0 deletions packages/control/chargelog/chargelog.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ def collect_data(chargepoint):
log_data.ev = get_value_or_default(lambda: chargepoint.data.set.charging_ev_data.num, 0)
log_data.prio = get_value_or_default(lambda: chargepoint.data.control_parameter.prio, False)
log_data.rfid = get_value_or_default(lambda: chargepoint.data.set.rfid)
log_data.odometer = get_value_or_default(lambda: charging_ev.data.get.odometer)
log_data.imported_since_mode_switch = get_value_or_default(
lambda: chargepoint.data.get.imported - log_data.imported_at_mode_switch, 0)
log_data.exported_since_mode_switch = get_value_or_default(
Expand Down Expand Up @@ -270,6 +271,7 @@ def _create_entry(chargepoint, charging_ev):
"chargemode": get_value_or_default(lambda: log_data.chargemode_log_entry),
"prio": get_value_or_default(lambda: log_data.prio),
"rfid": get_value_or_default(lambda: log_data.rfid),
"odometer": get_value_or_default(lambda: log_data.odometer),
"soc_at_start": get_value_or_default(lambda: log_data.soc_at_start),
"soc_at_end": get_value_or_default(lambda: charging_ev.data.get.soc),
"range_at_start": get_value_or_default(lambda: log_data.range_at_start),
Expand Down
1 change: 1 addition & 0 deletions packages/control/chargepoint/chargepoint_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ class Log:
soc_at_end: Optional[int] = None
range_at_start: Optional[float] = None
range_at_end: Optional[float] = None
odometer: Optional[float] = None


def connected_vehicle_factory() -> ConnectedVehicle:
Expand Down
1 change: 1 addition & 0 deletions packages/control/ev/ev.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ class Get:
force_soc_update: bool = field(default=False, metadata={
"topic": "get/force_soc_update"})
range: Optional[float] = field(default=None, metadata={"topic": "get/range"})
odometer: Optional[float] = field(default=None, metadata={"topic": "get/odometer"})
fault_state: int = field(default=0, metadata={"topic": "get/fault_state"})
fault_str: str = field(default=NO_ERROR, metadata={"topic": "get/fault_str"})

Expand Down
1 change: 1 addition & 0 deletions packages/helpermodules/measurement_logging/process_log.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ def get_default_charge_log_columns() -> Dict:
"vehicle_chargemode": True,
"vehicle_prio": True,
"vehicle_rfid": True,
"vehicle_odometer": False,
"vehicle_soc_at_start": False,
"vehicle_soc_at_end": False,
"chargepoint_name": True,
Expand Down
2 changes: 2 additions & 0 deletions packages/helpermodules/setdata.py
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,8 @@ def process_vehicle_topic(self, msg: mqtt.MQTTMessage):
self._validate_value(msg, float, [(0, 100)])
elif "/get/range" in msg.topic:
self._validate_value(msg, float, [(0, 1000)])
elif "/get/odometer" in msg.topic:
self._validate_value(msg, float, [(0, 9999999)])
elif "/get/force_soc_update" in msg.topic:
self._validate_value(msg, bool)
else:
Expand Down
13 changes: 11 additions & 2 deletions packages/helpermodules/update_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@

class UpdateConfig:

DATASTORE_VERSION = 113
DATASTORE_VERSION = 114

valid_topic = [
"^openWB/bat/config/bat_control_permitted$",
Expand Down Expand Up @@ -2955,6 +2955,15 @@ def upgrade(topic: str, payload) -> None:
f"'{device_name}' auf Typ '{new_type}'."),
MessageType.INFO,
)

self._loop_all_received_topics(upgrade)
self._append_datastore_version(113)

def upgrade_datastore_114(self) -> None:
def upgrade(topic: str, payload) -> None:
if "openWB/general/charge_log_data_config" == topic:
config = decode_payload(payload)
if config.get("vehicle_odometer") is None:
config["vehicle_odometer"] = False
return {topic: config}
self._loop_all_received_topics(upgrade)
self._append_datastore_version(114)
7 changes: 6 additions & 1 deletion packages/modules/common/component_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,11 +155,15 @@ def __init__(

@auto_str
class CarState:
def __init__(self, soc: float, range: Optional[float] = None, soc_timestamp: Optional[float] = None):
def __init__(self, soc: float,
range: Optional[float] = None,
soc_timestamp: Optional[float] = None,
odometer: Optional[float] = None):
"""Args:
soc: actual state of charge in percent
range: actual range in km
soc_timestamp: timestamp of last request as unix timestamp
odometer: actual odometer of vehicle in km
"""
self.soc = soc
self.range = range
Expand All @@ -170,6 +174,7 @@ def __init__(self, soc: float, range: Optional[float] = None, soc_timestamp: Opt
log.debug(f'Zeitstempel {soc_timestamp} ist in ms, wird in s gewandelt. Modul sollte angepasst werden.')
soc_timestamp /= 1000
self.soc_timestamp = soc_timestamp
self.odometer = odometer


@auto_str
Expand Down
5 changes: 4 additions & 1 deletion packages/modules/common/configurable_vehicle.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ def _get_carstate_by_source(self, vehicle_update_data: VehicleUpdateData, source
if source == SocSource.API:
try:
_carState = self.__component_updater(vehicle_update_data)
_odometer = _carState.odometer
_now = int(time.time())
_diff = 0
if _carState.soc_timestamp:
Expand All @@ -137,7 +138,8 @@ def _get_carstate_by_source(self, vehicle_update_data: VehicleUpdateData, source
vehicle_update_data.plug_state and\
vehicle_update_data.last_soc and\
vehicle_update_data.last_soc_timestamp >= vehicle_update_data.plug_time and\
(self.calculated_soc_state.last_imported or vehicle_update_data.imported):
vehicle_update_data.imported -\
(self.calculated_soc_state.last_imported or vehicle_update_data.imported) > 0:
_age = int(self.general_config.request_interval_charging / 60)
_txt1 = "Ladestand und Reichweite sind berechnet, da der von der Online-Abfrage "
_txt1 = _txt1 + f"gelieferte Zeitstempel mehr als {_age} min alt ist."
Expand All @@ -146,6 +148,7 @@ def _get_carstate_by_source(self, vehicle_update_data: VehicleUpdateData, source
_carState = calc_vehicle_data.calc_vehicle_data(vehicle_update_data,
self.calculated_soc_state.last_imported or
vehicle_update_data.imported)
_carState.odometer = _odometer
except Exception as e:
if vehicle_update_data.plug_state and\
vehicle_update_data.last_soc and\
Expand Down
1 change: 1 addition & 0 deletions packages/modules/common/store/_car.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ def update(self):
pub_to_broker("openWB/set/vehicle/"+str(self.vehicle_id)+"/get/soc", self.state.soc, 2)
pub_to_broker("openWB/set/vehicle/"+str(self.vehicle_id)+"/get/range", self.state.range, 2)
pub_to_broker("openWB/set/vehicle/"+str(self.vehicle_id)+"/get/soc_timestamp", self.state.soc_timestamp)
pub_to_broker("openWB/set/vehicle/"+str(self.vehicle_id)+"/get/odometer", self.state.odometer, 2)


def get_car_value_store(id: int) -> ValueStore[CarState]:
Expand Down
3 changes: 3 additions & 0 deletions packages/modules/update_soc.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ def _get_threads(self) -> Tuple[List[Thread], List[Thread]]:
f"EV{ev.num}: Nach dreimaliger erfolgloser SoC-Abfrage wird ein SoC von 0% angenommen.")
Pub().pub(f"openWB/set/vehicle/{ev.num}/get/soc", 0)
Pub().pub(f"openWB/set/vehicle/{ev.num}/get/range", None)
Pub().pub(f"openWB/set/vehicle/{ev.num}/get/odometer", None)
# Es wird ein Zeitstempel gesetzt, unabhängig ob die Abfrage erfolgreich war, da einige
# Hersteller bei zu häufigen Abfragen Accounts sperren.
Pub().pub(f"openWB/set/vehicle/{ev.num}/get/soc_request_timestamp",
Expand All @@ -86,6 +87,8 @@ def _get_threads(self) -> Tuple[List[Thread], List[Thread]]:
Pub().pub(f"openWB/set/vehicle/{ev.num}/get/soc_request_timestamp", None)
if ev.data.get.range is not None:
Pub().pub(f"openWB/set/vehicle/{ev.num}/get/range", None)
if ev.data.get.odometer is not None:
Pub().pub(f"openWB/set/vehicle/{ev.num}/get/odometer", None)
except Exception:
log.exception("Fehler im update_soc-Modul")
return threads_update, threads_store
Expand Down
3 changes: 2 additions & 1 deletion packages/modules/vehicles/mqtt/soc.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ def on_message(client, userdata, message):
topic_prefix = f"openWB/mqtt/vehicle/{vehicle}/get/"
return CarState(soc=received_topics.get(f"{topic_prefix}soc"),
range=received_topics.get(f"{topic_prefix}range"),
soc_timestamp=received_topics.get(f"{topic_prefix}soc_timestamp"))
soc_timestamp=received_topics.get(f"{topic_prefix}soc_timestamp"),
odometer=received_topics.get(f"{topic_prefix}odometer"))
else:
configurable_vehicle.fault_state.warning(
f"Keine MQTT-Daten für Fahrzeug {vehicle_config.name} empfangen oder es werden "
Expand Down
11 changes: 6 additions & 5 deletions packages/modules/vehicles/ovms/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,17 +187,18 @@ async def _fetch_soc(self,
if float(self.range) > 1000.0:
self.range = str(float(self.range) / 10)

self.kms = float(statusDict['odometer']) / 10
self.odometer = float(statusDict['odometer']) / 10
self.vehicle12v = statusDict['vehicle12v']
self.soc_ts = statusDict['m_msgtime_s']
self.soc_tsdt = datetime.strptime(self.soc_ts, date_fmt)
self.soc_tsdtL = utc2local(self.soc_tsdt)
self.soc_tsX = datetime.timestamp(self.soc_tsdtL)

log.info("soc=" + self.soc + ", range=" + self.range + ", soc_ts=" + str(self.soc_tsdtL))
log.info("OVMS: soc=" + self.soc + ", range=" + self.range + ", soc_ts=" + str(self.soc_tsdtL) +
", odometer=" + str(self.odometer))
log.debug("statusDict=\n" + dumps(statusDict, indent=4))

return int(float(self.soc)), float(self.range), self.soc_tsX
return int(float(self.soc)), float(self.range), self.soc_tsX, float(self.odometer)


# sync function
Expand All @@ -209,6 +210,6 @@ def fetch_soc(conf: OVMS, vehicle: int) -> Union[int, float, str]:

# get soc, range from server
a = api()
soc, range, soc_ts = loop.run_until_complete(a._fetch_soc(conf, vehicle))
soc, range, soc_ts, odometer = loop.run_until_complete(a._fetch_soc(conf, vehicle))

return soc, range, soc_ts
return soc, range, soc_ts, odometer
4 changes: 2 additions & 2 deletions packages/modules/vehicles/ovms/soc.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@


def fetch(vehicle_update_data: VehicleUpdateData, config: OVMS, vehicle: int) -> CarState:
soc, range, soc_ts = api.fetch_soc(config, vehicle)
return CarState(soc=soc, range=range, soc_timestamp=soc_ts)
soc, range, soc_ts, odometer = api.fetch_soc(config, vehicle)
return CarState(soc=soc, range=range, soc_timestamp=soc_ts, odometer=odometer)


def create_vehicle(vehicle_config: OVMS, vehicle: int):
Expand Down
6 changes: 5 additions & 1 deletion packages/modules/vehicles/vwgroup/vwgroup.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@ async def request_data(self, library) -> Union[int, float, str]:
soc_tsdtL = self.utc2local(soc_tsdtZ)
self.soc_tsX = datetime.timestamp(soc_tsdtL)
self.soc_ts = datetime.strftime(soc_tsdtL, ts_fmt)
if self.su.keys_exist(self.data, 'charging', 'batteryStatus', 'value', 'odometer'):
self.odometer = float(self.data['charging']['batteryStatus']['value']['odometer'])
else:
self.odometer = None
except Exception as e:
raise Exception("soc/range/soc_ts field missing exception: e=" + str(e))

Expand Down Expand Up @@ -131,4 +135,4 @@ async def request_data(self, library) -> Union[int, float, str]:
if (library.tokens['accessToken'] != self.accessTokenOld): # modified accessToken?
self.su.write_token_file(self.accessTokenFile, library.tokens['accessToken'])

return self.soc, self.range, self.soc_ts, self.soc_tsX
return self.soc, self.range, self.soc_ts, self.soc_tsX, self.odometer
15 changes: 11 additions & 4 deletions packages/modules/vehicles/vwid/libvwid.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ class Services:
CHARGING = "charging"
PARAMETERS = "parameters"
SERVICE_STATUS = "service_status"
MEASUREMENTS = "measurements"


def find_path_in_dict(src, path) -> object:
Expand Down Expand Up @@ -310,6 +311,7 @@ async def update(self):
self.get_selectivestatus(
[
Services.CHARGING,
Services.MEASUREMENTS,
]
)
)
Expand Down Expand Up @@ -419,7 +421,7 @@ async def expired(self, service):
expiration = datetime.now(UTC) + timedelta(days=1)
expiration = expiration.replace(tzinfo=None)
if now >= expiration:
_LOGGER.warning("Access to %s has expired!", service)
_LOGGER.info("Access to %s has expired!", service)
self._discovered = False
return True
except Exception:
Expand Down Expand Up @@ -1425,12 +1427,13 @@ async def get_status(self):
data['charging']['batteryStatus']['value']['currentSOC_pct'] = str(0)
data['charging']['batteryStatus']['value']['cruisingRangeElectric_km'] = str(0)
data['charging']['batteryStatus']['value']['carCapturedTimestamp'] = _now
data['charging']['batteryStatus']['value']['odometer'] = None

_k = str(vwid.connection.keys())
_LOGGER.info(f"libvwid.get_status connections at entry: vwid.connections.keys={_k}")
_LOGGER.debug(f"libvwid.get_status connections at entry: vwid.connections.keys={_k}")
_update_result = False
if self.username not in vwid.connection:
_LOGGER.info(f"create new connection, key={self.username}")
_LOGGER.debug(f"create new connection, key={self.username}")
vwid.connection[self.username] = Connection(session, self.username, self.password)
self._connection = vwid.connection[self.username]
vwid.connection[self.username]._session_tokens['identity'] = {}
Expand All @@ -1440,7 +1443,7 @@ async def get_status(self):
vwid.connection[self.username]._session_tokens['Legacy'][token] = self.tokens[token]
_conn_reuse = False
else:
_LOGGER.info(f"reuse existing connection, key={self.username}")
_LOGGER.debug(f"reuse existing connection, key={self.username}")
vwid.connection[self.username]._session = session
_conn_reuse = True
if not _conn_reuse:
Expand Down Expand Up @@ -1472,6 +1475,7 @@ async def get_status(self):
range =\
vehicle._states['charging']['batteryStatus']['value']['cruisingRangeElectric_km']
ts = vehicle._states['charging']['batteryStatus']['value']['carCapturedTimestamp']
odometer = vehicle._states['measurements']['odometerStatus']['value']['odometer']
_LOGGER.debug("vehicle =" + str(vehicle))
_LOGGER.debug("soc =" + str(soc))
_LOGGER.debug("range =" + str(range))
Expand All @@ -1481,10 +1485,13 @@ async def get_status(self):
data['charging']['batteryStatus']['value']['currentSOC_pct'] = str(soc)
data['charging']['batteryStatus']['value']['cruisingRangeElectric_km'] = str(range)
data['charging']['batteryStatus']['value']['carCapturedTimestamp'] = str(tsxx)
data['charging']['batteryStatus']['value']['odometer'] = str(odometer)
_LOGGER.debug("return data =" + to_json(data, indent=4))
for token in vwid.connection[self.username]._session_tokens['identity']:
self.tokens[token] =\
vwid.connection[self.username]._session_tokens['identity'][token]
_LOGGER.info("VWID: soc=" + str(soc)+", range=" + str(range) + "@" + str(tsxx) +
', odometer=' + str(odometer))
return data
else:
_LOGGER.error(f"SOCERR-02: Fahrzeug mit VIN {self.vin} nicht gefunden")
Expand Down
4 changes: 2 additions & 2 deletions packages/modules/vehicles/vwid/soc.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ async def _fetch_soc() -> Union[int, float, str]:

loop = new_event_loop()
set_event_loop(loop)
soc, range, soc_ts, soc_tsX = loop.run_until_complete(_fetch_soc())
return CarState(soc=soc, range=range, soc_timestamp=soc_tsX)
soc, range, soc_ts, soc_tsX, odometer = loop.run_until_complete(_fetch_soc())
return CarState(soc=soc, range=range, soc_timestamp=soc_tsX, odometer=odometer)

vw_group = VwGroup(vehicle_config, vehicle)

Expand Down
1 change: 1 addition & 0 deletions web/settings/downloadChargeLog.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"vehicle chargemode" => ["header" => "Lademodus", "type" => "chargemode"],
"vehicle prio" => ["header" => "Priorität", "type" => "bool"],
"vehicle rfid" => ["header" => "ID-Tag", "type" => "string"],
"vehicle odometer" => ["header" => "Kilometerstand", "type" => "int"],
"vehicle soc_at_start" => ["header" => "SoC Beginn", "type" => "int"],
"vehicle soc_at_end" => ["header" => "SoC Ende", "type" => "int"],
"vehicle range_at_start" => ["header" => "Reichweite Beginn", "type" => "range"],
Expand Down
Loading