Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 37 additions & 13 deletions packages/modules/vehicles/vwid/libeuda.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ def get_oidc_state(brand: str = DEFAULT_BRAND) -> str:
DATA_PATH = Path(__file__).resolve().parents[4] / "data" / "modules" / "vwid"
JSON_PATH = Path(str(RAMDISK_PATH) + '/vweuda')
storeFileName = '/data_'
MODULE_TYPE = 'vwid'

# VIN-Brand map
VIN_BRAND_MAP = {
Expand Down Expand Up @@ -792,9 +793,25 @@ def update_result_from_payload(self, payload: dict, source: str) -> dict:
"""Parse an EUDA payload and publish it in the in-memory result cache."""
vin = payload['vin']
result = parse_vehicle_data(payload)
euda.result[vin] = result
_valid = True
_LOGGER.info(f"latest result=\n{json.dumps(result, indent=4)}")
if vin in euda.result:
_LOGGER.info(f"cache result=\n{json.dumps(euda.result[vin], indent=4)}")
if result['soc_timestamp'] < euda.result[vin]['soc_timestamp']:
_LOGGER.info("thread result skipped, soc_timestamp too old")
_valid = False
if result['soc'] is None:
_LOGGER.info("thread result skipped, no soc found")
_valid = False
if _valid:
if vin in euda.result and result['odometer'] < euda.result[vin]['odometer']:
_LOGGER.info("odometer less than earlier - keep earlier value")
result['odometer'] = euda.result[vin]['odometer']
euda.result[vin] = result
_LOGGER.info("thread result is valid")

_ano_j = {ano_vin(vin): euda.result[vin]}
_LOGGER.info(f"thread result:\n{json.dumps(_ano_j, indent=4)}")
_LOGGER.info(f"\n{json.dumps(_ano_j, indent=4)}")

return result

Expand All @@ -814,14 +831,22 @@ def load_latest_json_result(self, vin: str) -> bool:
_LOGGER.exception(f"failed to load latest EUDA JSON {latest}: {err}")
return False

async def get_module_type(self, vehicle: int) -> str:
topic = f"openWB/vehicle/{vehicle}/soc_module/config"
conf = os.popen(f"mosquitto_sub -C 1 -t {topic}").read()
_LOGGER.debug(f"thread loop: conf={conf}")
_type = json.loads(conf)['type']
return _type

# eudaThread
async def async_eudaThread(self, username: str, password: str, vin: str, vehicle: int):
if vin[0:3] not in VIN_BRAND_MAP:
_LOGGER.warning(f"VIN {vin[0:3]} not in brand map, use {DEFAULT_BRAND}")
brand = VIN_BRAND_MAP.get(vin[0:3], DEFAULT_BRAND)
_LOGGER.info(f"async Thread started, brand={brand}")
try:
async with aiohttp.ClientSession(headers={'Connection': 'keep-alive'}) as session:
async with aiohttp.ClientSession(headers={'Connection': 'keep-alive'},
connector_owner=False) as session:
client_id = f"{vehicle}"
_k = str(euda.client.keys())
_LOGGER.info(f"libeuda.Thread client at entry: euda.client.keys={_k}")
Expand Down Expand Up @@ -851,15 +876,14 @@ async def async_eudaThread(self, username: str, password: str, vin: str, vehicle
identifier = meta.get("Identifier")

# thread main loop
while True:
topic = f"openWB/vehicle/{vehicle}/soc_module/config"
conf = os.popen(f"mosquitto_sub -C 1 -t {topic}").read()
_LOGGER.debug(f"thread loop: conf={conf}")
_type = json.loads(conf)['type']
_LOGGER.info(f"thread loop: _type={_type}")
if _type != 'vwid':
_active = True
while _active:
_type = await self.get_module_type(vehicle)
_LOGGER.info(f"thread loop: ev{vehicle} module type={_type}")
if _type != MODULE_TYPE:
Comment on lines +879 to +883

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Die Logik, dass der Thread dauerhaft läuft und über die Broker-Config beendet wird, bleibt so erhalten.
Was spricht dagegen, die Abfrage über das übliche SoC-Intervall zu starten und dann max 15 Minuten auf Daten zu warten und beim Erhalt von Daten den Thread zu beenden?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Das Portal stellt alle 15 min Daten zur Verfügung, die das Modul abholen sollte.
Manche der Datensets sind auch mal unvollständig oder mit älterem Zeitstempel als frühere Daten.
Das SoC-Modul soll alle Daten auswerten und im CarState den sinnvollsten Status melden.
Wenn nur im Abfrage-Intervall am Portal abgefragt wird, kann es passieren daß immer mal wieder so ein "schlechtes" Datenset als letztes genommen würde.
Deshalb soll asynchrone PollingThread laufen solange das Modul aktiv/konfiguriert ist.
Das entspricht auch dem Vorgehen im Homeassistant, dort wird auch ständig abgefragt.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dann kann der Thread 15 Min laufen und dann das sinnvollste Datenpaket nehmen, was er in dieser Zeit bekommen hat?
Homeassistant hat eine komplett andere Software-Architektur als openWB.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Das Portal hat noch viele erratische Verhaltensweisen, die ich mit dem asynchronen Vorgehen so gut wie möglich ausbügeln kann, vor allem wird die gesamte Historie der von Portal bereitgestellten Daten benutzt.

Ich habe beim Portal-Support ca. 8 Tickets offen, die verschiedene Probleme ansprechen, leider bisher ohne Reaktion.

Ich würde daher den aktuellen Ansatz erst mal so lassen.
Das Modul wird voraussichtlich etlichen Support-Aufwand generieren, wenn mehr Benutzer das Modul nutzen würden, das die Probleme des Portals erst mal bei jedem hochkochen werden.
Das ist schlecht wegen der vielen Rückfragen und gut weil man die Benutzer an den Portal-Support verweisen kann.
Vielleicht erzeugt das dann genug Druck bei VW/Cariad, das Portal zu verbessern.

Wenn es für eine Freigabe in eine openWB (Patch-) Release nicht geeignet sein sollte, ist das für mich auch OK.
Vielleicht findet sich ja auch jemand, der das noch anpassen will.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ich habe jetzt noch Plausibilitätschecks eingebaut, damit ein schon erreichtes Ergebnis nicht durch ältere oder fehlende Daten wieder verschlechtert wird.

  • Wenn im neuesten Datenpaket kein soc gefunden wird, wird das Paket verworfen.
  • Wenn der soc_timestamp älter ist als der schon vorhandene, wird das Paket verworfen.
  • Wenn odometer kleiner ist als der schon bekannte, bleibt es bei dem bekannten KM-Stand.

Das grundsätzliche Konstrukt, dass der parallel Thread permanent läuft und alle verfügbaren und sinnvollen Daten in den CarState einarbeitet muß ich beibehalten.
Wenn sich der Thread immer wieder beenden würde kann das m.E. nicht funktionieren.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, dann lassen wir das erstmal so und das VW-Modul bleibt aus dem Patch draußen.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK für mich, bei der aktuellen Datenqualität würde es im Patch vermutlich auch zuviel Rückfragen und Frustration geben.
Im master wäre es irgendwann aber schon gut.

Bevor es weiter als im master ist würde ich auch noch weiteres anpassen:

  • Modul umbenennen von vwid in z.B. vweuda
  • im UI ein vweuda mit passender Beschreibungen anlegen.
  • in update_config.py die bisherigen Module vwid, skoda, cupra auf das vweuda umbiegen.
  • Die dann obsoleten Module vwid, skoda und cupra incl. UIs aus git entfernen.

Ich arbeite momentan auch an einem neuen SoC-Modul für das Tibber-API, das eine Reihe von EV-Herstellern ausser VW & Co unterstützt und die Daten in meinen Tests sehr zeitnah und bisher zuverlässig liefert (soc, range, soc_timestamp). KM-Stand/odometer habe ich dort noch nicht gefunden.
Wird aber noch etwas dauern.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Guter Plan

_LOGGER.info(f"vehicle {vehicle} is not using module vwid: terminate now")
return
_active = False
continue
try:
_data = None
while _data is None:
Expand Down Expand Up @@ -969,11 +993,11 @@ async def get_status(self,

wait_until = time.time() + INITIAL_RESULT_WAIT
while self.vin not in euda.result and time.time() < wait_until:
_LOGGER.info(f"wait for first EUDA result for VIN {self.vin}")
_LOGGER.info(f"wait for first EUDA result for VIN {ano_vin(self.vin)}")
time.sleep(1)

if self.vin in euda.result:
_LOGGER.debug(f"vehicle match: {self.vin}")
_LOGGER.debug(f"vehicle match: {ano_vin(self.vin)}")
_ano_j = {}
for vin in euda.result:
_ano_j[ano_vin(vin)] = euda.result[vin]
Expand Down
Loading