services: services¶
obd_diag.services.diagnostics¶
.. py:module:: obd_diag.services.diagnostics
Diagnose-Abläufe: Fehlercodes lesen und mit Klartext anreichern.
.. py:function:: check_low_voltage(elm, voltage) :module: obd_diag.services.diagnostics
Bestätigt eine niedrige Adapter-Messung (ATRV) mit der Steuergerätespannung.
Der Adapter misst an Pin 16 der Diagnosebuchse, oft hinter einer Schutzdiode: Am
Mercedes A 180 d (W177) zeigte er 11,2 V, das Motorsteuergerät meldete 12,0 V.
Liegt voltage unter LOW_VOLTAGE, wird daher PID 42 gelesen und, wenn
plausibel, statt voltage geliefert (auch wenn er niedriger ist). Sonst, auch
bei None, bleibt es bei voltage. Nur nach ausgehandeltem Protokoll
aufrufen (sonst liefe die Protokollsuche mit der kurzen Wartezeit).
.. py:class:: DtcKind(*values) :module: obd_diag.services.diagnostics
Bases: :py:class:~enum.StrEnum
.. py:class:: DiagnosticCode(code: str, kind: obd_diag.services.diagnostics.DtcKind, info: obd_diag.data.dtc_catalog.DtcInfo | None = None, online: obd_diag.services.dtc_online.OnlineExplanation | None = None) :module: obd_diag.services.diagnostics
Bases: :py:class:object
.. py:class:: ScanResult(adapter: str, protocol: str, voltage: float | None, codes: list[obd_diag.services.diagnostics.DiagnosticCode] =
Bases: :py:class:object
.. py:class:: DtcLookup(*args, **kwargs) :module: obd_diag.services.diagnostics
Bases: :py:class:~typing.Protocol
Was scan vom Katalog braucht; erfüllt z. B. von DtcCatalog.
.. py:function:: scan(elm, catalog, lang=None) :module: obd_diag.services.diagnostics
Initialisiert den Adapter und liest gespeicherte, ausstehende und permanente Codes.
Nur lesend: es wird nichts gelöscht (kein Mode 04). lang: Sprache der
Katalogtexte, ohne Angabe die eingestellte (obd_diag.i18n).
.. py:function:: add_online_explanations(result, manufacturer, lookup=None) :module: obd_diag.services.diagnostics
Ergänzt Codes ohne Katalogtext um eine Online-Erklärung (nur nach Opt-in).
Codes mit Katalogtext werden nicht nachgeschlagen; ohne solche Codes geht nichts
ins Netz. manufacturer kommt aus der FIN, sonst None.
.. py:function:: scan_to_dict(result) :module: obd_diag.services.diagnostics
JSON-taugliche Form eines Scans (wie obd-diag scan --json).
obd_diag.services.clear¶
.. py:module:: obd_diag.services.clear
Fehlercodes löschen (Mode 04): die einzige schreibende Aktion des Tools.
Ablauf: Vorbedingungen prüfen (Zündung an, Motor aus), Codes und Freeze Frame sichern, erst dann löschen und zur Kontrolle neu einlesen. Schlägt irgendein Schritt vor dem Löschen fehl, wird Mode 04 nicht gesendet.
Derzeit gesperrt (CLEAR_ENABLED): Erst wenn das Lesen an einem echten Fahrzeug
geprüft ist, wird Löschen freigegeben. Die Sperre greift in clear_codes vor der
ersten Anfrage; CLI und GUI fragen clear_enabled zusätzlich vorher ab.
.. py:function:: clear_enabled() :module: obd_diag.services.clear
Ob Löschen freigegeben ist (zur Laufzeit gelesen, damit Tests es umstellen können).
.. py:exception:: ClearRefused :module: obd_diag.services.clear
Bases: :py:class:Exception
Eine Vorbedingung ist nicht erfüllt; die Meldung ist für Nutzer gedacht.
.. py:class:: ClearResult(backup_path: pathlib.Path, before: obd_diag.services.diagnostics.ScanResult, after: obd_diag.services.diagnostics.ScanResult) :module: obd_diag.services.clear
Bases: :py:class:object
.. py:function:: clearable_codes(result) :module: obd_diag.services.clear
Die Codes aus result, die Mode 04 löschen würde.
.. py:function:: default_backup_dir() :module: obd_diag.services.clear
$XDG_DATA_HOME/obd-diag/backups (Standard: ~/.local/share/obd-diag/backups).
.. py:function:: check_preconditions(elm) :module: obd_diag.services.clear
Wirft ClearRefused, wenn nicht gelöscht werden darf (z. B. Motor läuft).
Geprüft wird: das Steuergerät antwortet (Zündung an), die Bordspannung ist nicht zu niedrig und die Drehzahl ist 0 (Motor aus), bei mehreren antwortenden Steuergeräten bei jedem. Ist eine Drehzahl nicht lesbar, wird vorsichtshalber ebenfalls abgelehnt. Sendet nur lesende Befehle.
.. py:function:: clear_codes(elm, catalog, *, backup_dir=None, lang=None) :module: obd_diag.services.clear
Löscht die Fehlercodes nach Prüfung und Sicherung und liest danach neu ein.
Reihenfolge: Scan, Vorbedingungen, Freeze Frame lesen, Sicherung schreiben, erst
dann Mode 04, zum Schluss Kontroll-Scan. ClearRefused vor dem Löschen heißt:
nichts wurde verändert. Lehnt das Steuergerät Mode 04 ab, bleibt die Sicherung
liegen und ClearRefused nennt den Grund. Ist Löschen gesperrt
(CLEAR_ENABLED), kommt ClearRefused sofort, ohne dass etwas gesendet wird.
lang: Sprache der Katalogtexte, ohne Angabe die eingestellte (obd_diag.i18n).
obd_diag.services.readiness¶
.. py:module:: obd_diag.services.readiness
Readiness-Monitore (Mode 01, PID 01): Status der Eigendiagnosen fürs Abgas-System.
Aufbau der vier Datenbytes A-D nach SAE J1979 (ISO 15031-5), Tabelle zu Mode 01 PID 01 wie in Wikipedia „OBD-II PIDs“ (Abschnitt „Service 01 PID 01“, abgerufen 2026-10)::
A7 MIL (Kontrollleuchte) an
A6-A0 Zahl der gespeicherten Fehlercodes (dieses Steuergeräts)
B3 0 = Ottomotor (spark ignition), 1 = Diesel (compression ignition)
B0/B4 Verbrennungsaussetzer unterstützt / nicht abgeschlossen
B1/B5 Kraftstoffsystem unterstützt / nicht abgeschlossen
B2/B6 Komponenten unterstützt / nicht abgeschlossen
B7 reserviert
C Bit n: Monitor n unterstützt
D Bit n: Monitor n nicht abgeschlossen
Bit Otto (B3 = 0) Diesel (B3 = 1)
0 Katalysator NMHC-Katalysator
1 Katalysatorheizung NOx-Nachbehandlung / SCR
2 Tankentlüftung (EVAP) reserviert
3 Sekundärluftsystem Ladedruck
4 Klimaanlagen-Kältemittel reserviert
5 Lambdasonde Abgassensor
6 Lambdasondenheizung Partikelfilter
7 AGR und/oder VVT AGR und/oder VVT
Bit C4/D4 (Otto: Kältemittel der Klimaanlage) ist in neueren J1979-Ausgaben
reserviert, wird aber weiter dekodiert, weil ältere Fahrzeuge es setzen. Reservierte
Bits werden ignoriert. Nicht unterstützte Monitore erscheinen mit NOT_SUPPORTED.
Bezug zur Abgasuntersuchung (AU): Das Ergebnis heißt bewusst „alle Tests abgeschlossen“ und nicht „AU-bereit“. Nach der AU-Richtlinie (Verkehrsblatt 19/2017, Leitfaden 5.01, gültig ab 01.01.2018) besteht die AU bei allen OBD-Fahrzeugen aus einer Funktionsprüfung OBD und einer Endrohrmessung; die Bewertung der Prüfbereitschaftstests entfällt, wenn alle unterstützten Tests durchgeführt sind (Hella Gutmann, „Informationen zum Leitfaden 5.01“, BD0059, 12/2017). Schon vorher galt: Ist der Readiness-Code nicht gesetzt, wird stattdessen gemessen; das allein ist kein erheblicher Mangel. Eine zitierfähige, allgemeine Liste tolerierter offener Monitore gibt es nicht (Stand 2026-10-07), die Behandlung hängt von Fahrzeug und Prüfablauf ab. Deshalb gibt es hier keine AU-Bewertung.
.. py:class:: MonitorState(*values) :module: obd_diag.services.readiness
Bases: :py:class:~enum.StrEnum
.. py:class:: Monitor(key: str, name: str, state: obd_diag.services.readiness.MonitorState) :module: obd_diag.services.readiness
Bases: :py:class:object
.. py:class:: ReadinessStatus(mil_on: bool, dtc_count: int, compression_ignition: bool, monitors: tuple[obd_diag.services.readiness.Monitor, …]) :module: obd_diag.services.readiness
Bases: :py:class:object
.. py:property:: ReadinessStatus.all_complete :module: obd_diag.services.readiness :type: bool
Alle unterstützten Monitore abgeschlossen.
Das ist kein AU-Ergebnis: Seit der AU-Richtlinie 2018 (Leitfaden 5.01) gehört zur
AU immer auch die Endrohrmessung; offene Monitore führen nicht pauschal zum
Nichtbestehen, und ob einzelne toleriert werden, hängt von Fahrzeug und
Prüfablauf ab (siehe Doku, docs/benutzen/readiness.md).
.. py:property:: ReadinessStatus.ready :module: obd_diag.services.readiness :type: bool
Alter Name für ``all_complete`` (bleibt zur Kompatibilität).
.. py:function:: specific_monitors(compression_ignition) :module: obd_diag.services.readiness
Motorspezifische Monitore in Byte C/D als (Bit, Schlüssel, Name).
Gleicher Aufbau in PID 01 und PID 41 (services/readiness_assist.py).
.. py:function:: decode_readiness(data) :module: obd_diag.services.readiness
Dekodiert die vier Datenbytes A-D der Antwort auf 0101.
Weitere Bytes werden ignoriert; ValueError bei weniger als vier.
.. py:function:: combine_readiness(statuses) :module: obd_diag.services.readiness
Fasst die Antworten mehrerer Steuergeräte zu einem Gesamtstand zusammen.
Das Ergebnis hängt nicht von der Reihenfolge der Antworten ab. Die Motorart kommt
aus _ignition_type; Steuergeräte mit anderer Motorart-Kennung zählen nur für
die allgemeinen Monitore aus Byte B (Aussetzer, Kraftstoffsystem, Komponenten),
weil ihre Bits C/D anders belegt sind. MIL: an, wenn ein Steuergerät sie meldet;
Codezahl: Summe (Mode 03 liefert ja auch die Codes aller Steuergeräte). Je Monitor
zählt der schlechteste Stand: nicht abgeschlossen vor abgeschlossen vor nicht
unterstützt.
.. py:function:: read_readiness(elm) :module: obd_diag.services.readiness
None, wenn das Steuergerät PID 01 nicht beantwortet.
Antworten mehrere Steuergeräte (z. B. Motor und Getriebe), werden sie mit
combine_readiness zusammengefasst. Negative oder zu kurze Antworten zählen als
nicht beantwortet. ElmError bei Antworten, die keine Hex-Daten sind.
obd_diag.services.readiness_assist¶
.. py:module:: obd_diag.services.readiness_assist
Readiness-Assistent: welche Tests noch offen sind und welche Fahrt sie abschließt.
Grundlage sind zwei Mode-01-PIDs nach SAE J1979 (Wikipedia „OBD-II PIDs“, Service 01, PID 01 und PID 41, abgerufen 2026-10):
PID 01 meldet den Stand seit dem Löschen der Fehlercodes (
services/readiness.py).PID 41 meldet den Stand in diesem Fahrzyklus, also seit dem Einschalten der Zündung bzw. dem Motorstart. Byte A ist reserviert, die Bytes B bis D sind aufgebaut wie bei PID 01, nur heißt das erste Bit je Monitor nicht „unterstützt“, sondern „in diesem Zyklus freigegeben“ (0 = ausgesetzt oder nicht vorhanden), das zweite „in diesem Zyklus nicht abgeschlossen“. Ein nicht freigegebener Monitor meldet dort immer 0.
Welche Belegung Byte C/D hat (Otto oder Diesel), kommt aus der zusammengefassten
Antwort auf PID 01 (combine_readiness), nicht aus Byte B von PID 41: dessen Bit 3 ist
nicht in allen Ausgaben der Norm als Motorart belegt. Antworten mehrere Steuergeräte,
zählt je Monitor der schlechteste Stand (noch offen vor abgeschlossen vor ausgesetzt);
das hängt nicht von der Reihenfolge der Antworten ab (ohne Header wechselt sie am W177
bei jeder Anfrage).
Die Fahrhinweise sind allgemeine Erfahrungswerte, keine Norm: J1979 legt nur fest, wie
der Status gemeldet wird. Wann ein Monitor läuft (Temperaturen, Geschwindigkeit, Dauer,
Tankfüllung), bestimmt jeder Hersteller selbst, und oft braucht es mehrere Fahrten.
Gesendet werden nur lesende Anfragen: initialize, protocol, 0101, 0141.
.. py:class:: CycleState(*values) :module: obd_diag.services.readiness_assist
Bases: :py:class:~enum.StrEnum
.. py:class:: CycleMonitor(key: str, name: str, state: obd_diag.services.readiness_assist.CycleState) :module: obd_diag.services.readiness_assist
Bases: :py:class:object
.. py:class:: DriveCycleStatus(monitors) :module: obd_diag.services.readiness_assist
Bases: :py:class:object
Antwort auf PID 41: Stand der Monitore in diesem Fahrzyklus.
.. py:function:: decode_drive_cycle(data, compression_ignition) :module: obd_diag.services.readiness_assist
Dekodiert die vier Datenbytes A-D der Antwort auf 0141.
compression_ignition (aus PID 01) wählt die Belegung von Byte C/D. Weitere Bytes
werden ignoriert; ValueError bei weniger als vier.
.. py:function:: combine_drive_cycle(statuses) :module: obd_diag.services.readiness_assist
Je Monitor der schlechteste Stand aller Steuergeräte (unabhängig von der Reihenfolge).
Alle Antworten müssen mit derselben Motorart dekodiert sein.
.. py:function:: read_drive_cycle(elm, compression_ignition) :module: obd_diag.services.readiness_assist
0141; None, wenn kein Steuergerät PID 41 beantwortet.
Negative oder zu kurze Antworten zählen als nicht beantwortet, mehrere Steuergeräte
werden mit combine_drive_cycle zusammengefasst. ElmError bei Antworten, die
keine Hex-Daten sind.
.. py:class:: Advice(key, name, cycle, hint) :module: obd_diag.services.readiness_assist
Bases: :py:class:object
Ein seit dem Löschen offener Test mit Stand dieser Fahrt und Fahrhinweis.
.. py:function:: advise(readiness, cycle) :module: obd_diag.services.readiness_assist
Die seit dem Löschen offenen Tests in der Reihenfolge von PID 01.
.. py:function:: notes(readiness, cycle, *, cycle_read=True) :module: obd_diag.services.readiness_assist
Allgemeine Hinweise (deutsch, zur Anzeige tr); leer, wenn nichts offen ist.
cycle_read: PID 41 wurde abgefragt (cycle ist None also, weil das
Fahrzeug nicht antwortet, nicht weil niemand gefragt hat).
.. py:class:: ReadinessCheck(created, adapter, protocol, readiness, drive_cycle) :module: obd_diag.services.readiness_assist
Bases: :py:class:object
Ergebnis von check_readiness.
.. py:function:: check_readiness(elm) :module: obd_diag.services.readiness_assist
Adapter initialisieren, Protokoll aushandeln, PID 01 und PID 41 lesen; nur lesend.
Befehle: initialize (ATZ … ATSP0), protocol (0100, ATDPN, ATDP), 0101
und, wenn PID 01 beantwortet wurde, 0141. Ein ElmError bei PID 01 oder 41
lässt nur diesen Teil leer; Verbindungsfehler (TransportError) gehen an den
Aufrufer.
.. py:function:: check_to_dict(check) :module: obd_diag.services.readiness_assist
JSON-taugliche Form (obd-diag readiness --json); Texte in der eingestellten
Sprache, Schlüssel und Zustände wie im Code.
obd_diag.services.vehicle¶
.. py:module:: obd_diag.services.vehicle
Fahrzeug-Identifikation: FIN lesen (Mode 09, PID 02) und dekodieren.
Offline liefert eine WMI-Tabelle (FIN-Stellen 1-3) Hersteller und Land. Die NHTSA-vPIC-Abfrage ist optional, nur nach Opt-in, und wird je FIN lokal gecacht.
Regeln der Dekodierung (decode_vin):
Gültig: genau 17 Zeichen aus 0-9 und A-Z ohne I, O, Q (ISO 3779).
Prüfziffer (Stelle 9, Gewichte nach 49 CFR 565.15 / ISO 3779 Anhang): vorgeschrieben nur für Nordamerika (WMI beginnt mit 1-5) und China (
L, GB 16735); dortTrue/False. Sonst (z. B. Europa, oftZan Stelle 9)True, wenn sie zufällig oder freiwillig stimmt, undNone, wenn nicht: ein Abweichen ist dort kein Fehler.Modelljahr (Stelle 10): der Code wiederholt sich alle 30 Jahre (
A= 1980 oder 2010). In Nordamerika entscheidet Stelle 7 (49 CFR 565.15: Ziffer = 1980-2009, Buchstabe = 2010-2039); das gilt dort für Pkw, MPV und Lkw bis 10.000 lb, nicht für schwerere Fahrzeuge, Busse und Motorräder, wird hier aber für alle FIN mit1-5angewandt. Sonst gilt als beste Schätzung (model_year) das jüngste Jahr, das nicht mehr als ein Jahr in der Zukunft liegt (Modelljahre beginnen vor dem Kalenderjahr); das 30 Jahre ältere Jahr steht dann inmodel_year_alternatives, sofern es nicht vor 1980 liegt (Beginn der Codes) und zum Protokoll passt (unten). Außerhalb Nordamerikas ist Stelle 10 nicht überall als Modelljahr genutzt, die Angabe ist dort ein Hinweis, keine Gewissheit. Bekannt anders belegt ist sie bei Mercedes-Benz (MODEL_YEAR_NOT_ENCODED): Stelle 10 ist die Lenkung (1Linkslenker), Stelle 11 das Werk; Beispiel WDD 169 007-1J-236589 in Wikipedia „Fahrzeug-Identifizierungsnummer“ (https://de.wikipedia.org/wiki/Fahrzeug-Identifizierungsnummer, abgerufen 2026-10-09). Am A 180 d (W177, Baujahr nach 2018) ergab sie sonst „2001“. Für diese WMI gibt es kein Modelljahr.Plausibilität über das OBD-Protokoll (
decode_vin(vin, protocol=...), nur wenn die FIN aus dem Fahrzeug gelesen wurde): Spricht das Fahrzeug ein OBD-II-Protokoll (SAE J1850, ISO 9141-2, ISO 14230-4, ISO 15765-4), sind Modelljahre vor 1994 unplausibel (die OBD-II-Spezifikation entstand um 1994, Pflicht in den USA ab Modelljahr 1996, EOBD in der EU ab 2001 Benziner/2004 Diesel). Bei CAN nach ISO 15765-4 sind Jahre vor 2000 unplausibel (CAN war für OBD-II in den USA erst ab Modelljahr 2003 zulässig; 2000 lässt Spielraum). Quelle: Wikipedia „On-board diagnostics“ (https://en.wikipedia.org/wiki/On-board_diagnostics, abgerufen 2026-10-07). Solche Jahre fallen aus den Alternativen; die beste Schätzung bleibt unverändert, sie ist ohnehin das jüngere Jahr. SAE J1939 (Nutzfahrzeuge) und unbekannte Protokolle schränken nichts ein.
.. py:class:: VinInfo(vin: str, valid: bool, checksum_ok: bool | None, wmi: str, manufacturer: str | None = None, country: str | None = None, model_year: int | None = None, online: dict[str, str] =
Bases: :py:class:object
.. py:function:: model_year_text(info) :module: obd_diag.services.vehicle
z. B. „2026 oder 1996 (aus Stelle 10, ohne Gewähr)“; None ohne Modelljahr.
.. py:function:: checksum_text(info) :module: obd_diag.services.vehicle
Prüfziffer als Text, gleich in Kommandozeile, Bericht und Oberfläche.
„stimmt“ auch dort, wo sie nicht vorgeschrieben ist; „nicht vorgeschrieben“ nur, wenn sie nicht stimmt, das aber kein Fehler ist (z. B. in Europa).
.. py:function:: parse_vin_response(response) :module: obd_diag.services.vehicle
FIN aus der Antwort auf 0902 (CAN mehrteilig oder ältere Protokolle).
None, wenn die Antwort keine 17-stellige FIN enthält (z. B. nur Füllbytes oder
abgelehnt); ValueError bei Zeilen, die keine Hex-Daten sind, und
FrameSequenceError (Unterklasse) bei vermischten Frames mehrerer Steuergeräte
(siehe split_messages).
.. py:function:: vin_from_messages(messages) :module: obd_diag.services.vehicle
Wie parse_vin_response, aber für bereits zerlegte Nachrichten.
.. py:function:: read_vin(elm) :module: obd_diag.services.vehicle
None, wenn das Fahrzeug Mode 09 PID 02 nicht unterstützt (vor ca. 2005).
Ebenso None (mit Log-Eintrag), wenn die Antwort keine gültig aussehende FIN
enthält; ElmError, wenn sie sich gar nicht zerlegen lässt. Senden mehrere
Steuergeräte ihre FIN gleichzeitig mehrteilig (ohne Header nicht zuzuordnen), wird
die Anfrage einmal mit Headern (ATH1) wiederholt.
.. py:function:: check_digit(vin) :module: obd_diag.services.vehicle
Prüfziffer (0-9 oder X) einer gültigen FIN nach 49 CFR 565.15.
.. py:function:: earliest_plausible_year(protocol) :module: obd_diag.services.vehicle
Frühestes plausibles Modelljahr für ein Fahrzeug, das dieses Protokoll spricht.
protocol ist die Bezeichnung laut Adapter (ATDP, z. B. „ISO 15765-4 (CAN
11/500)“); None, leer, SAE J1939 oder unbekannt schränken nichts ein.
.. py:function:: model_years(vin, *, today=None, earliest=1980) :module: obd_diag.services.vehicle
Mögliche Modelljahre aus Stelle 10, beste Schätzung zuerst; () bei unbekanntem Code.
Nordamerika: nur das Jahr nach Stelle 7. Sonst das jüngste Jahr bis ein Jahr in der
Zukunft und, falls nicht vor earliest, das 30 Jahre ältere.
.. py:function:: model_year(vin, *, today=None) :module: obd_diag.services.vehicle
Modelljahr (beste Schätzung) aus Stelle 10 einer gültigen FIN, None bei
unbekanntem Code.
.. py:function:: decode_vin(vin, *, protocol=None, today=None) :module: obd_diag.services.vehicle
Rein offline: Gültigkeit, Prüfziffer, WMI-Hersteller/Land, Modelljahr.
protocol: OBD-Protokoll des Fahrzeugs, aus dem die FIN stammt (Bezeichnung
laut Adapter); schließt unplausible ältere Modelljahre aus (siehe Moduldoku).
.. py:function:: default_vpic_cache_dir() :module: obd_diag.services.vehicle
$XDG_CACHE_HOME/obd-diag/vpic (Standard: ~/.cache/obd-diag/vpic).
.. py:function:: lookup_vpic(vin, *, cache_dir=None, timeout=5.0) :module: obd_diag.services.vehicle
Fragt NHTSA vPIC ab (nur aufrufen, wenn Nutzer zugestimmt hat).
Ergebnisse werden unter cache_dir (Standard: $XDG_CACHE_HOME/obd-diag/vpic)
je FIN abgelegt; eine FIN ändert sich nie, ein Abruf pro Fahrzeug reicht.
Netzwerkfehler ergeben ein leeres dict.
obd_diag.services.session¶
.. py:module:: obd_diag.services.session
Diagnosesitzung: alles, was ein Durchgang ausliest, plus Speichern und Export.
.. py:class:: Session(created: datetime.datetime, scan: obd_diag.services.diagnostics.ScanResult, readiness: obd_diag.services.readiness.ReadinessStatus | None = None, freeze_frame: obd_diag.protocol.obd.FreezeFrame | None = None, vehicle: obd_diag.services.vehicle.VinInfo | None = None, clear_counters: obd_diag.services.used_car.ClearCounters | None = None, ecus: tuple[obd_diag.protocol.vehicle_info.EcuInfo, …] | None = None, tests: tuple[obd_diag.protocol.monitor_tests.TestResult, …] | None = None) :module: obd_diag.services.session
Bases: :py:class:object
.. py:function:: run_diagnosis(elm, catalog, lang=None, *, online_vin_lookup=False, online_dtc_lookup=False) :module: obd_diag.services.session
Scan, Readiness, Freeze Frame, FIN, Zähler seit dem Löschen, Steuergeräte-Angaben (Mode 09) und Testergebnisse (Mode 06) in einem Durchgang; nur lesend.
Fehlt eine einzelne Angabe (Steuergerät antwortet nicht), bleibt sie None;
nur ein fehlgeschlagener Scan bricht ab.
Der Scan setzt den Adapter zurück (ATZ) und handelt das Protokoll aus; danach
folgen Readiness (0101), Freeze Frame (02xx00), FIN (0902), die Zähler
für den Gebrauchtwagen-Check (0130, 0131, 014E, 0121, 014D), Mode 09
(0900 und die gemeldeten InfoTypes) und Mode 06 (0600 … und die gemeldeten
MIDs), die beiden letzten mit Headern. Ein
ElmError in einem dieser Teile lässt nur diesen Teil leer; Verbindungsfehler
(TransportError) brechen ab. Der Freeze Frame wird immer gelesen, bleibt aber
None, wenn er weder einen auslösenden Code noch Werte enthält (ohne
gespeicherten Code ist er leer). Die FIN geht nur mit online_vin_lookup an
NHTSA vPIC. Mit online_dtc_lookup bekommen Codes ohne Katalogtext eine
Online-Erklärung (services/dtc_online; die FIN verlässt dabei nie den Rechner,
nur der Hersteller entscheidet, welche Datei geladen wird).
.. py:function:: default_session_dir() :module: obd_diag.services.session
$XDG_DATA_HOME/obd-diag/sessions (Standard: ~/.local/share/obd-diag/sessions).
.. py:function:: session_to_dict(session) :module: obd_diag.services.session
JSON-taugliche Form; session_from_dict kehrt sie um.
Der Scan hat dieselbe Form wie bei obd-diag scan --json.
.. py:function:: session_from_dict(data) :module: obd_diag.services.session
Gegenstück zu session_to_dict; ValueError bei fremden oder kaputten Daten.
.. py:function:: save_session(session, directory=None) :module: obd_diag.services.session
Speichert als JSON mit Zeitstempel im Namen; überschreibt nie.
Dateiname: session-YYYYmmdd-HHMMSS.json nach session.created, bei
Namensgleichheit mit -2, -3 usw.
.. py:function:: load_session(path) :module: obd_diag.services.session
Liest eine mit save_session gespeicherte Sitzung.
ValueError (Meldung in der eingestellten Sprache), wenn die Datei kein gültiges
JSON oder keine obd-diag-Sitzung ist; OSError, wenn sie sich nicht lesen lässt.
obd_diag.services.compare¶
.. py:module:: obd_diag.services.compare
Sitzungsvergleich: was sich zwischen zwei gespeicherten Diagnosen geändert hat.
Arbeitet nur auf gespeicherten Daten (Session), spricht also nie mit dem Fahrzeug.
Die ältere Sitzung ist „vorher“, die jüngere „nachher“ (nach Session.created).
Jeder Teil der Sitzung hat eine eigene Vergleichsfunktion in SECTIONS
(Schlüssel, Überschrift, Funktion). Kommen neue Daten in die Sitzung (z. B. Mode 06
oder weitere PIDs), bekommen sie dort eine eigene Funktion; die Ausgabe in
Kommandozeile und Oberfläche zeigt jeden Abschnitt ohne weitere Änderung an.
Texte entstehen beim Vergleich in der eingestellten Sprache (tr); nach einem
Sprachwechsel einfach neu vergleichen. Fehlercode-Titel bleiben in der Sprache des Scans.
.. py:class:: ChangeKind(*values) :module: obd_diag.services.compare
Bases: :py:class:~enum.StrEnum
.. py:class:: Trend(*values) :module: obd_diag.services.compare
Bases: :py:class:~enum.StrEnum
.. py:class:: Change(kind: obd_diag.services.compare.ChangeKind, subject: str, before: str | None, after: str | None, trend: obd_diag.services.compare.Trend = <Trend.NEUTRAL: ‘neutral’>) :module: obd_diag.services.compare
Bases: :py:class:object
.. py:class:: Section(key: str, title: str, changes: tuple[obd_diag.services.compare.Change, …]) :module: obd_diag.services.compare
Bases: :py:class:object
.. py:property:: Section.differences :module: obd_diag.services.compare :type: tuple[~obd_diag.services.compare.Change, …]
Alles außer ``UNCHANGED``.
.. py:class:: SessionComparison(before: obd_diag.services.session.Session, after: obd_diag.services.session.Session, sections: tuple[obd_diag.services.compare.Section, …], swapped: bool) :module: obd_diag.services.compare
Bases: :py:class:object
.. py:property:: SessionComparison.same_vehicle :module: obd_diag.services.compare :type: bool | None
Gleiche FIN? ``None``, wenn eine der Sitzungen keine FIN hat.
.. py:function:: change_text(change) :module: obd_diag.services.compare
Wert einer Änderung als Text, z. B. „ausstehend → gespeichert“ oder „neu: gespeichert“.
.. py:function:: compare_sessions(first, second) :module: obd_diag.services.compare
Vergleicht zwei Sitzungen; die ältere gilt als „vorher“ (Reihenfolge egal).
.. py:function:: comparison_to_dict(comparison) :module: obd_diag.services.compare
JSON-taugliche Form (obd-diag compare --json); Texte in der eingestellten Sprache.
obd_diag.services.storage¶
.. py:module:: obd_diag.services.storage
Ablage unter $XDG_DATA_HOME/obd-diag (Sicherungen, Sitzungen, Mitschnitte).
.. py:function:: data_dir() :module: obd_diag.services.storage
$XDG_DATA_HOME/obd-diag (Standard: ~/.local/share/obd-diag).
.. py:function:: trace_dir() :module: obd_diag.services.storage
Ablage der Adapter-Mitschnitte (obd-diag ... --trace).
.. py:function:: write_new_json(directory, stem, data) :module: obd_diag.services.storage
Schreibt data atomar als <stem>.json (belegt: <stem>-2.json usw.).
Vorhandene Dateien werden nie überschrieben, und unter dem endgültigen Namen erscheint nur eine vollständig geschriebene Datei.
obd_diag.services.live¶
.. py:module:: obd_diag.services.live
Live-Daten (Mode 01) fortlaufend lesen und als CSV aufzeichnen; nur lesend.
Ablauf: prepare_live initialisiert den Adapter und ermittelt die unterstützten
Werte, run_live fragt die gewählten Werte reihum ab, bis should_stop True
liefert oder max_samples erreicht ist. Die Bordspannung wird regelmäßig gelesen;
unter LOW_VOLTAGE wird die Abfrage gedrosselt.
Gesendet werden nur initialize/protocol (AT-Befehle, 0100), ATRV und
die 01xx-Anfragen aus protocol.pids.
.. py:exception:: SelectionError :module: obd_diag.services.live
Bases: :py:class:ValueError
Die gewünschten Werte lassen sich nicht abfragen (unbekannt, nicht unterstützt
oder leere Auswahl). Andere ValueError stammen aus kaputten Antworten.
.. py:class:: LiveSample(elapsed: float, values: dict[str, float | None], voltage: float | None, throttled: bool, alarms: tuple[obd_diag.services.alarms.Alarm, …] = ()) :module: obd_diag.services.live
Bases: :py:class:object
.. py:class:: LiveSetup(adapter: str, protocol: str, available: list[obd_diag.protocol.pids.PidSpec]) :module: obd_diag.services.live
Bases: :py:class:object
.. py:function:: prepare_live(elm) :module: obd_diag.services.live
Adapter initialisieren, Protokoll aushandeln, unterstützte Live-Werte ermitteln.
Befehle: initialize (ATZ … ATSP0), protocol (0100, ATDPN, ATDP), dann
pids.read_supported_pids (0100, ggf. 0120 …). Fehler gehen an den Aufrufer.
.. py:function:: select_pids(setup, keys, *, skip_unsupported=False) :module: obd_diag.services.live
Die gewünschten Werte (None: DEFAULT_KEYS), soweit unterstützt.
Unbekannte Schlüssel ergeben SelectionError; bekannte, aber nicht unterstützte
ebenfalls (Meldung nennt sie), damit der Nutzer nicht stumm leere Spalten bekommt.
Bei None oder skip_unsupported (Auswahl getroffen, bevor bekannt war, was
das Fahrzeug kann) werden nicht unterstützte Werte stillschweigend weggelassen.
Reihenfolge: wie in keys bzw. DEFAULT_KEYS; doppelte Schlüssel einmal.
Bleibt kein Wert übrig, ist das ebenfalls ein SelectionError: eine Abfrage
ohne Werte liest nichts.
.. py:class:: LiveRecorder(path, pids) :module: obd_diag.services.live
Bases: :py:class:object
Schreibt Live-Werte als CSV (UTF-8 mit BOM; Trennzeichen und Dezimalzeichen nach der Sprache wie export: Deutsch Semikolon und Komma, Englisch Komma und Punkt).
Kopfzeile: Zeit (s), dann je Wert Name (Einheit), Bordspannung (V) und
zuletzt Warnungen (in der eingestellten Sprache). Die Warnungen einer Runde
stehen als Text in einer Zelle, mehrere durch | getrennt.
Nicht lesbare Werte bleiben leere Zellen. Jede Zeile wird sofort geschrieben
(flush), damit ein Abbruch nichts verliert. Die Datei wird beim Erzeugen exklusiv
angelegt; existiert sie schon, gibt es FileExistsError (nie überschreiben).
.. py:method:: LiveRecorder.add(sample) :module: obd_diag.services.live
Hängt eine Runde als Zeile an (sofort auf die Platte).
.. py:function:: alarm_text(alarm, pids) :module: obd_diag.services.live
Text einer Warnung in der eingestellten Sprache, Name und Einheit aus pids.
.. py:function:: recording_dir() :module: obd_diag.services.live
$XDG_DATA_HOME/obd-diag/recordings.
.. py:function:: new_recording_path(directory) :module: obd_diag.services.live
Freier Name live-YYYYmmdd-HHMMSS[-n].csv; legt den Ordner an.
.. py:function:: run_live(elm, pids, *, on_sample, should_stop, interval=1.0, max_samples=None, recorder=None, thresholds=None, clock=
Fragt pids reihum ab und meldet jede Runde über on_sample.
Erwartet einen mit prepare_live vorbereiteten Adapter. Jede PID wird je Runde
einmal abgefragt, auch wenn mehrere Werte aus ihr stammen. Ein einzelner nicht
lesbarer Wert (ElmError oder ValueError) ergibt None in der Runde
(kein Abbruch); TransportError bricht ab. Scheitert in MAX_FAILED_ROUNDS
Runden nacheinander jede Abfrage an einem Adapterfehler (Bus weg, Zündung aus), endet
die Abfrage mit ElmError. should_stop wird vor jeder Runde
und während des Wartens geprüft (in Schritten von höchstens 0,1 s).
Die Bordspannung wird in Runde 0 und jeder VOLTAGE_EVERY-ten Runde gelesen;
liegt sie unter LOW_VOLTAGE, beginnen Runden nur alle LOW_VOLTAGE_INTERVAL
Sekunden (throttled), und sie wird jede Runde gelesen, damit die Drosselung
endet, sobald die Spannung wieder reicht (Motor angesprungen). Ist sie einmal nicht
lesbar, bleibt die Drosselung, wie sie war (eine schwache Batterie wird davon nicht
besser). Runden beginnen im Abstand interval ab Rundenbeginn; dauert eine Runde
länger, folgt die nächste sofort.
Mit thresholds (services.alarms) trägt jede Runde die überschrittenen
Warnschwellen in LiveSample.alarms; gesendet wird dafür nichts.
Liefert die Anzahl der Runden.
obd_diag.services.used_car¶
.. py:module:: obd_diag.services.used_car
Gebrauchtwagen-Check: Wurden die Fehlercodes vor kurzem gelöscht? Nur lesend.
Beim Löschen (Mode 04) setzt jedes Steuergerät nach SAE J1979 diese Zähler (Mode 01) auf null, und sie laufen danach mit dem Fahren wieder hoch:
PID
30: Warmläufe seit dem Löschen (0 bis 255; ein Warmlauf: Kühlmittel um mindestens 22 °C gestiegen und mindestens 70 °C erreicht)PID
31: Strecke seit dem Löschen in km (0 bis 65535)PID
4E: Zeit seit dem Löschen in Minuten (0 bis 65535)
Dazu zwei Zähler zur Motorkontrollleuchte (MIL), die beim Löschen ebenfalls auf null
gehen: PID 21 (Strecke mit Warnleuchte an, km) und 4D (Zeit mit Warnleuchte an,
Minuten). Permanente Codes (Mode 0A) lassen sich gar nicht löschen; sie kommen aus dem
Scan, Warnleuchte und offene Tests aus der Readiness (PID 01).
Gefragt wird jede PID einmal, ohne vorher die unterstützten PIDs zu lesen (NO DATA
heißt: nicht gemeldet). Antworten mehrere Steuergeräte, zählt bei den Zählern seit dem
Löschen der kleinste Wert und bei den Warnleuchten-Zählern der größte: Ohne Header kommen
die Antworten in wechselnder Reihenfolge (W177), und für die Frage „kürzlich gelöscht?“
zählt das Steuergerät, das es am deutlichsten zeigt.
Die Grenzen für „kürzlich“ (RECENT_*) sind eine Faustregel, keine Norm: Nach dem
Löschen brauchen die Readiness-Tests meist einige Fahrten über ein paar hundert Kilometer,
bis sie wieder abgeschlossen sind. Auch ein Batterie- oder Steuergerätewechsel und jede
Werkstatt, die Codes löscht, setzen die Zähler zurück; ein Befund ist also ein Grund
nachzufragen, kein Beweis.
.. py:class:: ClearCounters(warmups=None, distance_km=None, time_min=None, mil_distance_km=None, mil_time_min=None) :module: obd_diag.services.used_car
Bases: :py:class:object
Zähler seit dem letzten Löschen der Fehlercodes; None: nicht gemeldet.
.. py:property:: ClearCounters.available :module: obd_diag.services.used_car :type: bool
Mindestens ein Zähler seit dem Löschen (30, 31, 4E) ist bekannt.
.. py:function:: read_clear_counters(elm) :module: obd_diag.services.used_car
Liest PID 30, 31, 4E, 21 und 4D (je eine Anfrage, in dieser Reihenfolge).
Ein Adapterfehler (ElmError) lässt nur diesen Zähler leer; Verbindungsfehler
(TransportError) gehen an den Aufrufer. Nur nach ausgehandeltem Protokoll
aufrufen.
.. py:class:: UsedCarReading(scan, readiness, counters) :module: obd_diag.services.used_car
Bases: :py:class:object
Was der Check braucht: Scan (permanente Codes), Readiness, Zähler.
.. py:function:: read_used_car(elm) :module: obd_diag.services.used_car
Nur der Gebrauchtwagen-Check (obd-diag used-car): Scan, 0101, Zähler.
Die vollständige Diagnose (run_diagnosis) liest dasselbe mit; ohne Katalog,
denn der Check zeigt nur die Codes selbst.
.. py:class:: Verdict(*values) :module: obd_diag.services.used_car
Bases: :py:class:~enum.StrEnum
.. py:class:: Level(*values) :module: obd_diag.services.used_car
Bases: :py:class:~enum.StrEnum
.. py:class:: Finding(level: obd_diag.services.used_car.Level, text: str) :module: obd_diag.services.used_car
Bases: :py:class:object
.. py:class:: UsedCarCheck(verdict: obd_diag.services.used_car.Verdict, recently_cleared: bool, findings: tuple[obd_diag.services.used_car.Finding, …]) :module: obd_diag.services.used_car
Bases: :py:class:object
.. py:function:: counter_rows(counters) :module: obd_diag.services.used_car
Bezeichnung und Wert je Zähler in der eingestellten Sprache, auch „nicht gemeldet“.
.. py:function:: assess(counters, scan, readiness) :module: obd_diag.services.used_car
Bewertet Zähler, permanente Codes, Warnleuchte und offene Tests.
Texte in der eingestellten Sprache; bei einem Sprachwechsel neu aufrufen.
counters ist None bei Sitzungen, in denen die Zähler nicht gelesen wurden.
obd_diag.services.battery¶
.. py:module:: obd_diag.services.battery
Batterie- und Anlasstest: Ruhespannung, Einbruch beim Anlassen, Ladespannung; nur lesend.
Ablauf (run_battery_test), der Nutzer startet den Motor selbst:
Vorbereitung:
initialize,protocol; Drehzahl (010C) muss 0 sein (Motor aus, Zündung an), Kühlmitteltemperatur (0105) für die Grenze beim Anlassen.Ruhe (
REST_SECONDS): Bordspannung laut Adapter (ATRV) in schneller Folge, am Anfang und Ende die Steuergerätespannung (PID 42).Anlassen: Aufforderung, den Motor zu starten; bis
START_TIMEOUTwirdATRVso schnell gelesen, wie der Adapter antwortet (höchstens alleSAMPLE_INTERVAL), und außerhalb des Einbruchs alleRPM_INTERVALdie Drehzahl. Ein Einbruch um mehr alsCRANK_DROPunter die Ruhespannung gilt als Anlassen, abRUNNING_RPMläuft der Motor.Laden: nach
SETTLE_SECONDSEinschwingenCHARGE_SECONDSlangATRVund PID 42.
Warum ATRV für den Einbruch: Adapter-Befehle erreichen das Fahrzeug nicht, für sie gilt
die Mindestpause von 50 ms zwischen Anfragen ans Fahrzeug nicht (Elm327.command). Am
FORScan-Adapter (ELM327-Nachbau) dauerte ATRV etwa 15 ms, PID 42 mit Pause über
100 ms; ein Anlassvorgang dauert oft unter einer Sekunde. Der Adapter misst aber an Pin 16
hinter einer Schutzdiode: Am Mercedes A 180 d (W177) zeigte er in Ruhe und bei laufendem
Motor stets etwa 0,8 V weniger als das Motorsteuergerät. Die Differenz in Ruhe
(offset) wird deshalb auf alle ATRV-Werte aufgeschlagen, sofern das Steuergerät
PID 42 liefert. Ruhe- und Ladespannung kommen bevorzugt direkt aus PID 42.
Bricht die Spannung so tief ein, dass der Adapter neu startet (LV RESET oder seine
Kennung statt einer Spannung), wird er neu initialisiert und das im Ergebnis vermerkt.
Gesendet werden nur initialize/protocol (AT-Befehle, 0100), ATRV,
010C, 0105 und 0142; alles lesend.
Schwellen (Bewertung in rest_verdict, crank_verdict, charge_verdict):
Ruhe: Eine 12-V-Bleibatterie hat in Ruhe etwa 12,6 V voll, 12,4 V zu drei Vierteln, 12,2 V halb geladen, unter 12,0 V ist sie weitgehend leer (übliche Ladezustandstabelle, z. B. Battery University BU-903). Mit Zündung an ziehen Steuergeräte Strom, die Spannung liegt etwas tiefer; daher gut ab
REST_GOOD(12,2 V), darunter bisLOW_VOLTAGE(11,8 V, wie die Warnung der Diagnose) schwach, darunter entladen. Nach einer Fahrt liegt die Spannung durch Oberflächenladung eine Weile höher.Anlassen: Beim klassischen Belastungstest (halber Kälteprüfstrom, 15 s) darf die Spannung bei 21 °C nicht unter 9,6 V fallen, bei Kälte etwas tiefer (Tabelle
CRANK_LIMITS, wie in Werkstatthandbüchern nach BCI). Das gilt hier sinngemäß für den Tiefstwert beim Anlassen, ohne die erstenINRUSH_SKIPSekunden (Einschaltstrom des Anlassers). Die Kühlmitteltemperatur steht für die Batterietemperatur; bei warmem Motor ist sie höher und die Grenze damit die strengste. BisCRANK_MARGINdarüber: knapp.Laden im Leerlauf:
CHARGE_GOODbisCHARGE_MAXgut; darüber lädt der Regler zu hoch. ZwischenCHARGE_MINundCHARGE_GOODniedrig: bei Fahrzeugen mit Batteriemanagement (Start-Stopp, geregelte Lichtmaschine) oft gewollt. UnterCHARGE_MIN(unter der Ruhespannung einer vollen Batterie) lädt die Lichtmaschine nicht.
Die Schwellen sind Richtwerte für 12-V-Bleibatterien (auch AGM/EFB), keine Herstellerangabe.
.. py:class:: Phase(*values) :module: obd_diag.services.battery
Bases: :py:class:~enum.StrEnum
.. py:class:: Rating(*values) :module: obd_diag.services.battery
Bases: :py:class:~enum.StrEnum
.. py:exception:: BatteryTestRefused :module: obd_diag.services.battery
Bases: :py:class:Exception
Der Test kann nicht laufen (Motor läuft, keine Spannung lesbar); nichts gemessen.
.. py:exception:: BatteryTestCancelled :module: obd_diag.services.battery
Bases: :py:class:Exception
Vom Nutzer abgebrochen (should_stop).
.. py:class:: BatteryProgress(phase: obd_diag.services.battery.Phase, elapsed: float, voltage: float | None, rpm: float | None, remaining: float | None) :module: obd_diag.services.battery
Bases: :py:class:object
.. py:class:: BatteryTestResult(adapter: str, rest_adapter: float | None, rest_ecu: float | None, offset: float, coolant_temp: float | None, crank_detected: bool, crank_minimum: float | None, crank_lowest: float | None, crank_seconds: float | None, started: bool, charge_adapter: float | None, charge_ecu: float | None, adapter_reset: bool, curve: tuple[tuple[float, float], …]) :module: obd_diag.services.battery
Bases: :py:class:object
.. py:property:: BatteryTestResult.rest_voltage :module: obd_diag.services.battery :type: float | None
Ruhespannung: Steuergerät, sonst Adapter.
.. py:property:: BatteryTestResult.charge_voltage :module: obd_diag.services.battery :type: float | None
Ladespannung: Steuergerät, sonst Adapter (korrigiert).
.. py:function:: crank_limit(coolant_temp) :module: obd_diag.services.battery
Mindestspannung beim Anlassen für die Temperatur; ohne Angabe die strengste.
.. py:function:: run_battery_test(elm, *, on_progress=None, should_stop=<function
Führt den Test durch (siehe Modul); der Nutzer startet den Motor auf Phase.START.
on_progress meldet Phasenwechsel sofort, sonst höchstens alle
PROGRESS_INTERVAL. BatteryTestRefused: Motor läuft schon oder der Adapter
liefert keine Spannung. BatteryTestCancelled: should_stop wurde True.
TransportError (Adapter weg) geht durch, ebenso Fehler beim Initialisieren.
Springt der Motor nicht an, endet der Test nach start_timeout ohne Ladespannung.
.. py:class:: Verdict(rating: obd_diag.services.battery.Rating, text: str) :module: obd_diag.services.battery
Bases: :py:class:object
.. py:function:: overall_rating(result) :module: obd_diag.services.battery
Schlechteste der drei Bewertungen; UNKNOWN nur, wenn keine bewertet ist.
.. py:function:: notes(result) :module: obd_diag.services.battery
Hinweise zur Messung (in der eingestellten Sprache).
.. py:function:: result_to_dict(result) :module: obd_diag.services.battery
Für obd-diag battery --json; Schlüssel englisch wie bei den anderen Befehlen.