From c610cc023c8fceaef72458f6f2bb56906b19d856 Mon Sep 17 00:00:00 2001 From: Stefan Lohmaier Date: Tue, 12 May 2026 01:50:12 -0700 Subject: [PATCH] feat: Safety Goals + Drive-Away-Assist + vollst. Traceability Neue Layer: - safety/sg/SG-001..005 als eigene Doorstop-Items (ASIL D/D/A/C/B) - SYS-Reqs verlinken nach oben auf SG via frontmatter - Kette ist jetzt: SG -> SYS -> SA, SWE -> SWA -> Code (@arch) + Test (@reqs) Drive-Away-Assist im Safety Manager: - SWE-011 (Anfahrabsicht erkennen) implementiert - SWE-012 (Sicherheits-Check Tuer + Gurt) implementiert - Neuer State SAFETY_DRIVE_AWAY + safety_mgr_release_requested() - SafetyInputs erweitert um gas_pedal_percent, gear_in_drive, door_closed, seatbelt_fastened - 5 neue Tests (DRIVE_AWAY armed/blocked/end-conditions) - Test-Header @reqs erweitert auf SWE-007..012 traceability.py erweitert: - SG als neuer Top-Level - Code-Mapping-Check: @arch im Header von src/*.c muss SWA-id matchen - Test-Mapping-Check: @reqs im Header der Tests muss alle SWE der zugehoerigen SWA abdecken - HTML zeigt 7 Spalten: SG | SYS | SA | SWE | SWA | Code | Test - 2 zusaetzliche Tabellen: Code->Arch und Test->Reqs test_apply_controller.c: - @reqs Header um SWE-005 ergaenzt (war funktional drin, nur Tag fehlte) Counts: - 55 doorstop-Items (war 50) - 46 Unit-Tests (war 41) - Traceability vollstaendig in beide Richtungen --- docs/traceability/index.html | 157 +++++++++-- docs/traceability/matrix.json | 429 +++++++++++++++++++++-------- reqs/sys/SYS-001.md | 3 +- reqs/sys/SYS-002.md | 4 +- reqs/sys/SYS-003.md | 3 +- reqs/sys/SYS-004.md | 3 +- reqs/sys/SYS-005.md | 4 +- reqs/sys/SYS-006.md | 3 +- reqs/sys/SYS-007.md | 3 +- safety/sg/SG-001.md | 17 ++ safety/sg/SG-002.md | 17 ++ safety/sg/SG-003.md | 17 ++ safety/sg/SG-004.md | 17 ++ safety/sg/SG-005.md | 16 ++ src/safety_manager.c | 62 ++++- src/safety_manager.h | 21 +- tests/unit/test_apply_controller.c | 2 +- tests/unit/test_safety_manager.c | 91 +++++- tools/generate_doorstop_items.py | 75 ++++- tools/traceability.py | 268 ++++++++++++++---- 20 files changed, 998 insertions(+), 214 deletions(-) create mode 100644 safety/sg/SG-001.md create mode 100644 safety/sg/SG-002.md create mode 100644 safety/sg/SG-003.md create mode 100644 safety/sg/SG-004.md create mode 100644 safety/sg/SG-005.md diff --git a/docs/traceability/index.html b/docs/traceability/index.html index 4e92be4..b53afae 100644 --- a/docs/traceability/index.html +++ b/docs/traceability/index.html @@ -3,27 +3,150 @@ demo-epb — Traceability Matrix

demo-epb — Traceability Matrix

-

Generiert aus 50 Items (SYS: 10, SWE: 25, SA: 5, SWA: 10).

+

Vollstaendige Kette: SG → SYS → SA, SWE → SWA → Code (@arch) + Test (@reqs)

+

+SG: 5   +SYS: 10   +SWE: 25   +SA: 5   +SWA: 10   +Code-Files: 4   +Test-Files: 4 +

- - - - - - - - - - - -
System-RequirementSystem-Arch (SA)Software-Req (SWE)Software-Arch (SWA)
SYS-001 D
Halten der Parkbremse im Stillstand
SA-001 D
EPB ECU
SA-002 D
Aktoren (Caliper-Motoren)
SWE-001 D
Apply-Controller haelt Klemmkraft
SWE-002 D
Watchdog ueberwacht Apply-Controller
SWE-022 B
Stillstands-Erkennung aus Wheel Speeds
SWA-002 D
Apply Controller
SWA-004 B
Wheel Speed Plausibilisierung
SYS-002 D
Apply auf Fahrer-Anforderung
SA-001 D
EPB ECU
SA-002 D
Aktoren (Caliper-Motoren)
SWE-003 D
Schalter-Apply-Signal an Apply-Controller weiterleiten
SWE-004 D
Klemmkraft-Erreichen bestaetigen
SWE-022 B
Stillstands-Erkennung aus Wheel Speeds
SWE-025 QM
Switch-Debouncing
SWA-002 D
Apply Controller
SWA-004 B
Wheel Speed Plausibilisierung
SWA-006 QM
Switch Debouncer
SYS-003 B
Release auf Fahrer-Anforderung
SA-001 D
EPB ECU
SA-002 D
Aktoren (Caliper-Motoren)
SWE-005 B
Release-Voraussetzungen pruefen
SWE-006 B
Aktoren in Release-Position fahren
SWE-025 QM
Switch-Debouncing
SWA-002 D
Apply Controller
SWA-003 B
Actuator Driver
SWA-006 QM
Switch Debouncer
SYS-004 D
Auto-Apply bei Motor-Aus
SA-001 D
EPB ECU
SWE-007 D
Motor-Aus-Bedingung erkennen
SWE-008 D
Auto-Apply nach 2 s Verzoegerung
SWA-001 D
Safety Manager
SYS-005 D
Hill-Hold am Berg
SA-001 D
EPB ECU
SA-003 B
Sensor-Cluster
SWE-009 D
Hill-Hold-Aktivierungsbedingung
SWE-010 D
Hill-Hold-Uebergabe an Apply-Controller
SWE-024 B
Inclinometer Tiefpass-Filter
SWA-001 D
Safety Manager
SWA-005 B
Inclinometer Filter
SYS-006 B
Auto-Release beim Anfahren (Drive-Away-Assist)
SA-001 D
EPB ECU
SA-003 B
Sensor-Cluster
SWE-011 B
Anfahrabsicht erkennen
SWE-012 B
Sicherheits-Check vor Auto-Release
SWE-022 B
Stillstands-Erkennung aus Wheel Speeds
SWA-001 D
Safety Manager
SWA-004 B
Wheel Speed Plausibilisierung
SYS-007 B
Aktor-Stromueberwachung
SA-001 D
EPB ECU
SA-002 D
Aktoren (Caliper-Motoren)
SA-003 B
Sensor-Cluster
SWE-013 B
Strommessung mit 1 kHz
SWE-014 B
Overcurrent-Cutoff
SWE-015 B
Klemmkraft-Schaetzung aus Strom-Profil
SWE-023 B
Wheel Speed Plausibilisierung
SWA-003 B
Actuator Driver
SWA-004 B
Wheel Speed Plausibilisierung
SYS-008 QM
Service-Modus fuer Werkstatt
SA-001 D
EPB ECU
SA-004 QM
HMI (Schalter, LED, Display)
SWE-016 QM
UDS RoutineControl 0x31 fuer Service-Release
SWE-017 QM
Service-Mode-Indikator
SWA-009 QM
Service Mode
SYS-009 QM
UDS-Diagnose
SA-001 D
EPB ECU
SA-005 QM
CAN-Bus
SWE-018 QM
UDS Service 0x19 ReadDTC
SWE-019 QM
UDS Service 0x22 ReadDataByIdentifier
SWA-008 QM
Diagnostic Manager
SWA-010 QM
Logger
SYS-010 QM
HMI-Statusanzeige
SA-001 D
EPB ECU
SA-004 QM
HMI (Schalter, LED, Display)
SA-005 QM
CAN-Bus
SWE-020 QM
LED-Steuerung
SWE-021 QM
CAN-Status-Frame
SWA-007 QM
Display Manager
\ No newline at end of file +Safety GoalSystem-RequirementSystem-ArchSoftware-ReqSoftware-ArchCodeTest + +
SG-001 D
Kein ungewolltes Loesen der Parkbremse im Stillstand
+
SYS-001 D
Halten der Parkbremse im Stillstand
+
SA-001 D
EPB ECU
SA-002 D
Aktoren (Caliper-Motoren)
+
SWE-001 D
Apply-Controller haelt Klemmkraft
SWE-002 D
Watchdog ueberwacht Apply-Controller
SWE-022 B
Stillstands-Erkennung aus Wheel Speeds
+
SWA-002 D
Apply Controller
SWA-004 B
Wheel Speed Plausibilisierung
+
src/apply_controller.c
+
tests/unit/test_apply_controller.c
+ + +
SG-001 D
Kein ungewolltes Loesen der Parkbremse im Stillstand
+
SYS-004 D
Auto-Apply bei Motor-Aus
+
SA-001 D
EPB ECU
+
SWE-007 D
Motor-Aus-Bedingung erkennen
SWE-008 D
Auto-Apply nach 2 s Verzoegerung
+
SWA-001 D
Safety Manager
+
src/safety_manager.c
+
tests/unit/test_safety_manager.c
+ + +
SG-002 D
Kein ungewolltes Festklemmen waehrend der Fahrt
+
SYS-002 D
Apply auf Fahrer-Anforderung
+
SA-001 D
EPB ECU
SA-002 D
Aktoren (Caliper-Motoren)
+
SWE-003 D
Schalter-Apply-Signal an Apply-Controller weiterleiten
SWE-004 D
Klemmkraft-Erreichen bestaetigen
SWE-022 B
Stillstands-Erkennung aus Wheel Speeds
SWE-025 QM
Switch-Debouncing
+
SWA-002 D
Apply Controller
SWA-004 B
Wheel Speed Plausibilisierung
SWA-006 QM
Switch Debouncer
+
src/apply_controller.c
src/switch_debouncer.c
+
tests/unit/test_apply_controller.c
tests/unit/test_switch_debouncer.c
+ + +
SG-002 D
Kein ungewolltes Festklemmen waehrend der Fahrt
+
SYS-005 D
Hill-Hold am Berg
+
SA-001 D
EPB ECU
SA-003 B
Sensor-Cluster
+
SWE-009 D
Hill-Hold-Aktivierungsbedingung
SWE-010 D
Hill-Hold-Uebergabe an Apply-Controller
SWE-024 B
Inclinometer Tiefpass-Filter
+
SWA-001 D
Safety Manager
SWA-005 B
Inclinometer Filter
+
src/safety_manager.c
+
tests/unit/test_safety_manager.c
+ + +
SG-003 A
Schutz gegen Aktor-Ueberlast
+
SYS-007 B
Aktor-Stromueberwachung
+
SA-001 D
EPB ECU
SA-002 D
Aktoren (Caliper-Motoren)
SA-003 B
Sensor-Cluster
+
SWE-013 B
Strommessung mit 1 kHz
SWE-014 B
Overcurrent-Cutoff
SWE-015 B
Klemmkraft-Schaetzung aus Strom-Profil
SWE-023 B
Wheel Speed Plausibilisierung
+
SWA-003 B
Actuator Driver
SWA-004 B
Wheel Speed Plausibilisierung
+
src/actuator_driver.c
+
tests/unit/test_actuator_driver.c
+ + +
SG-004 C
Zuverlaessige Hill-Hold-Uebergabe
+
SYS-005 D
Hill-Hold am Berg
+
SA-001 D
EPB ECU
SA-003 B
Sensor-Cluster
+
SWE-009 D
Hill-Hold-Aktivierungsbedingung
SWE-010 D
Hill-Hold-Uebergabe an Apply-Controller
SWE-024 B
Inclinometer Tiefpass-Filter
+
SWA-001 D
Safety Manager
SWA-005 B
Inclinometer Filter
+
src/safety_manager.c
+
tests/unit/test_safety_manager.c
+ + +
SG-004 C
Zuverlaessige Hill-Hold-Uebergabe
+
SYS-006 B
Auto-Release beim Anfahren (Drive-Away-Assist)
+
SA-001 D
EPB ECU
SA-003 B
Sensor-Cluster
+
SWE-011 B
Anfahrabsicht erkennen
SWE-012 B
Sicherheits-Check vor Auto-Release
SWE-022 B
Stillstands-Erkennung aus Wheel Speeds
+
SWA-001 D
Safety Manager
SWA-004 B
Wheel Speed Plausibilisierung
+
src/safety_manager.c
+
tests/unit/test_safety_manager.c
+ + +
SG-005 B
Reaktion auf Fahreranforderung
+
SYS-002 D
Apply auf Fahrer-Anforderung
+
SA-001 D
EPB ECU
SA-002 D
Aktoren (Caliper-Motoren)
+
SWE-003 D
Schalter-Apply-Signal an Apply-Controller weiterleiten
SWE-004 D
Klemmkraft-Erreichen bestaetigen
SWE-022 B
Stillstands-Erkennung aus Wheel Speeds
SWE-025 QM
Switch-Debouncing
+
SWA-002 D
Apply Controller
SWA-004 B
Wheel Speed Plausibilisierung
SWA-006 QM
Switch Debouncer
+
src/apply_controller.c
src/switch_debouncer.c
+
tests/unit/test_apply_controller.c
tests/unit/test_switch_debouncer.c
+ + +
SG-005 B
Reaktion auf Fahreranforderung
+
SYS-003 B
Release auf Fahrer-Anforderung
+
SA-001 D
EPB ECU
SA-002 D
Aktoren (Caliper-Motoren)
+
SWE-005 B
Release-Voraussetzungen pruefen
SWE-006 B
Aktoren in Release-Position fahren
SWE-025 QM
Switch-Debouncing
+
SWA-002 D
Apply Controller
SWA-003 B
Actuator Driver
SWA-006 QM
Switch Debouncer
+
src/apply_controller.c
src/actuator_driver.c
src/switch_debouncer.c
+
tests/unit/test_actuator_driver.c
tests/unit/test_apply_controller.c
tests/unit/test_switch_debouncer.c
+ + +— +
SYS-008 QM
Service-Modus fuer Werkstatt
+
SA-001 D
EPB ECU
SA-004 QM
HMI (Schalter, LED, Display)
+
SWE-016 QM
UDS RoutineControl 0x31 fuer Service-Release
SWE-017 QM
Service-Mode-Indikator
+
SWA-009 QM
Service Mode
+— +— + + +— +
SYS-009 QM
UDS-Diagnose
+
SA-001 D
EPB ECU
SA-005 QM
CAN-Bus
+
SWE-018 QM
UDS Service 0x19 ReadDTC
SWE-019 QM
UDS Service 0x22 ReadDataByIdentifier
+
SWA-008 QM
Diagnostic Manager
SWA-010 QM
Logger
+— +— + + +— +
SYS-010 QM
HMI-Statusanzeige
+
SA-001 D
EPB ECU
SA-004 QM
HMI (Schalter, LED, Display)
SA-005 QM
CAN-Bus
+
SWE-020 QM
LED-Steuerung
SWE-021 QM
CAN-Status-Frame
+
SWA-007 QM
Display Manager
+— +— + + +

Code → Architektur

+ + + + + +
Datei@arch@reqs
src/safety_manager.cSWA-001SWE-007 SWE-008 SWE-009 SWE-010 SWE-011 SWE-012
src/apply_controller.cSWA-002SWE-001 SWE-002 SWE-003 SWE-004
src/actuator_driver.cSWA-003SWE-006 SWE-013 SWE-014 SWE-015
src/switch_debouncer.cSWA-006SWE-025
+

Test → Anforderungen

+ + + + + +
Test-DateiDecklt SWA@reqs
tests/unit/test_safety_manager.cSWA-001SWE-007 SWE-008 SWE-009 SWE-010 SWE-011 SWE-012
tests/unit/test_apply_controller.cSWA-002SWE-001 SWE-002 SWE-003 SWE-004 SWE-005
tests/unit/test_actuator_driver.cSWA-003SWE-006 SWE-013 SWE-014 SWE-015
tests/unit/test_switch_debouncer.cSWA-006SWE-025
+ \ No newline at end of file diff --git a/docs/traceability/matrix.json b/docs/traceability/matrix.json index 249864b..b583670 100644 --- a/docs/traceability/matrix.json +++ b/docs/traceability/matrix.json @@ -1,5 +1,9 @@ [ { + "sg": { + "id": "SG-001", + "asil": "D" + }, "sys": { "id": "SYS-001", "asil": "D", @@ -38,9 +42,58 @@ "id": "SWA-004", "asil": "B" } + ], + "code": [ + "src/apply_controller.c" + ], + "tests": [ + "tests/unit/test_apply_controller.c" ] }, { + "sg": { + "id": "SG-001", + "asil": "D" + }, + "sys": { + "id": "SYS-004", + "asil": "D", + "title": "Auto-Apply bei Motor-Aus" + }, + "sa": [ + { + "id": "SA-001", + "asil": "D" + } + ], + "swe": [ + { + "id": "SWE-007", + "asil": "D" + }, + { + "id": "SWE-008", + "asil": "D" + } + ], + "swa": [ + { + "id": "SWA-001", + "asil": "D" + } + ], + "code": [ + "src/safety_manager.c" + ], + "tests": [ + "tests/unit/test_safety_manager.c" + ] + }, + { + "sg": { + "id": "SG-002", + "asil": "D" + }, "sys": { "id": "SYS-002", "asil": "D", @@ -87,83 +140,21 @@ "id": "SWA-006", "asil": "QM" } + ], + "code": [ + "src/apply_controller.c", + "src/switch_debouncer.c" + ], + "tests": [ + "tests/unit/test_apply_controller.c", + "tests/unit/test_switch_debouncer.c" ] }, { - "sys": { - "id": "SYS-003", - "asil": "B", - "title": "Release auf Fahrer-Anforderung" + "sg": { + "id": "SG-002", + "asil": "D" }, - "sa": [ - { - "id": "SA-001", - "asil": "D" - }, - { - "id": "SA-002", - "asil": "D" - } - ], - "swe": [ - { - "id": "SWE-005", - "asil": "B" - }, - { - "id": "SWE-006", - "asil": "B" - }, - { - "id": "SWE-025", - "asil": "QM" - } - ], - "swa": [ - { - "id": "SWA-002", - "asil": "D" - }, - { - "id": "SWA-003", - "asil": "B" - }, - { - "id": "SWA-006", - "asil": "QM" - } - ] - }, - { - "sys": { - "id": "SYS-004", - "asil": "D", - "title": "Auto-Apply bei Motor-Aus" - }, - "sa": [ - { - "id": "SA-001", - "asil": "D" - } - ], - "swe": [ - { - "id": "SWE-007", - "asil": "D" - }, - { - "id": "SWE-008", - "asil": "D" - } - ], - "swa": [ - { - "id": "SWA-001", - "asil": "D" - } - ] - }, - { "sys": { "id": "SYS-005", "asil": "D", @@ -202,50 +193,19 @@ "id": "SWA-005", "asil": "B" } + ], + "code": [ + "src/safety_manager.c" + ], + "tests": [ + "tests/unit/test_safety_manager.c" ] }, { - "sys": { - "id": "SYS-006", - "asil": "B", - "title": "Auto-Release beim Anfahren (Drive-Away-Assist)" + "sg": { + "id": "SG-003", + "asil": "A" }, - "sa": [ - { - "id": "SA-001", - "asil": "D" - }, - { - "id": "SA-003", - "asil": "B" - } - ], - "swe": [ - { - "id": "SWE-011", - "asil": "B" - }, - { - "id": "SWE-012", - "asil": "B" - }, - { - "id": "SWE-022", - "asil": "B" - } - ], - "swa": [ - { - "id": "SWA-001", - "asil": "D" - }, - { - "id": "SWA-004", - "asil": "B" - } - ] - }, - { "sys": { "id": "SYS-007", "asil": "B", @@ -292,9 +252,238 @@ "id": "SWA-004", "asil": "B" } + ], + "code": [ + "src/actuator_driver.c" + ], + "tests": [ + "tests/unit/test_actuator_driver.c" ] }, { + "sg": { + "id": "SG-004", + "asil": "C" + }, + "sys": { + "id": "SYS-005", + "asil": "D", + "title": "Hill-Hold am Berg" + }, + "sa": [ + { + "id": "SA-001", + "asil": "D" + }, + { + "id": "SA-003", + "asil": "B" + } + ], + "swe": [ + { + "id": "SWE-009", + "asil": "D" + }, + { + "id": "SWE-010", + "asil": "D" + }, + { + "id": "SWE-024", + "asil": "B" + } + ], + "swa": [ + { + "id": "SWA-001", + "asil": "D" + }, + { + "id": "SWA-005", + "asil": "B" + } + ], + "code": [ + "src/safety_manager.c" + ], + "tests": [ + "tests/unit/test_safety_manager.c" + ] + }, + { + "sg": { + "id": "SG-004", + "asil": "C" + }, + "sys": { + "id": "SYS-006", + "asil": "B", + "title": "Auto-Release beim Anfahren (Drive-Away-Assist)" + }, + "sa": [ + { + "id": "SA-001", + "asil": "D" + }, + { + "id": "SA-003", + "asil": "B" + } + ], + "swe": [ + { + "id": "SWE-011", + "asil": "B" + }, + { + "id": "SWE-012", + "asil": "B" + }, + { + "id": "SWE-022", + "asil": "B" + } + ], + "swa": [ + { + "id": "SWA-001", + "asil": "D" + }, + { + "id": "SWA-004", + "asil": "B" + } + ], + "code": [ + "src/safety_manager.c" + ], + "tests": [ + "tests/unit/test_safety_manager.c" + ] + }, + { + "sg": { + "id": "SG-005", + "asil": "B" + }, + "sys": { + "id": "SYS-002", + "asil": "D", + "title": "Apply auf Fahrer-Anforderung" + }, + "sa": [ + { + "id": "SA-001", + "asil": "D" + }, + { + "id": "SA-002", + "asil": "D" + } + ], + "swe": [ + { + "id": "SWE-003", + "asil": "D" + }, + { + "id": "SWE-004", + "asil": "D" + }, + { + "id": "SWE-022", + "asil": "B" + }, + { + "id": "SWE-025", + "asil": "QM" + } + ], + "swa": [ + { + "id": "SWA-002", + "asil": "D" + }, + { + "id": "SWA-004", + "asil": "B" + }, + { + "id": "SWA-006", + "asil": "QM" + } + ], + "code": [ + "src/apply_controller.c", + "src/switch_debouncer.c" + ], + "tests": [ + "tests/unit/test_apply_controller.c", + "tests/unit/test_switch_debouncer.c" + ] + }, + { + "sg": { + "id": "SG-005", + "asil": "B" + }, + "sys": { + "id": "SYS-003", + "asil": "B", + "title": "Release auf Fahrer-Anforderung" + }, + "sa": [ + { + "id": "SA-001", + "asil": "D" + }, + { + "id": "SA-002", + "asil": "D" + } + ], + "swe": [ + { + "id": "SWE-005", + "asil": "B" + }, + { + "id": "SWE-006", + "asil": "B" + }, + { + "id": "SWE-025", + "asil": "QM" + } + ], + "swa": [ + { + "id": "SWA-002", + "asil": "D" + }, + { + "id": "SWA-003", + "asil": "B" + }, + { + "id": "SWA-006", + "asil": "QM" + } + ], + "code": [ + "src/apply_controller.c", + "src/actuator_driver.c", + "src/switch_debouncer.c" + ], + "tests": [ + "tests/unit/test_actuator_driver.c", + "tests/unit/test_apply_controller.c", + "tests/unit/test_switch_debouncer.c" + ] + }, + { + "sg": null, "sys": { "id": "SYS-008", "asil": "QM", @@ -325,9 +514,12 @@ "id": "SWA-009", "asil": "QM" } - ] + ], + "code": [], + "tests": [] }, { + "sg": null, "sys": { "id": "SYS-009", "asil": "QM", @@ -362,9 +554,12 @@ "id": "SWA-010", "asil": "QM" } - ] + ], + "code": [], + "tests": [] }, { + "sg": null, "sys": { "id": "SYS-010", "asil": "QM", @@ -399,6 +594,8 @@ "id": "SWA-007", "asil": "QM" } - ] + ], + "code": [], + "tests": [] } ] \ No newline at end of file diff --git a/reqs/sys/SYS-001.md b/reqs/sys/SYS-001.md index 5a2b5f3..20689ac 100644 --- a/reqs/sys/SYS-001.md +++ b/reqs/sys/SYS-001.md @@ -5,7 +5,8 @@ header: 'Halten der Parkbremse im Stillstand' level: 1.1 normative: true reviewed: null -links: [] +links: + - SG-001 asil: D --- diff --git a/reqs/sys/SYS-002.md b/reqs/sys/SYS-002.md index c256486..b22acae 100644 --- a/reqs/sys/SYS-002.md +++ b/reqs/sys/SYS-002.md @@ -5,7 +5,9 @@ header: 'Apply auf Fahrer-Anforderung' level: 1.2 normative: true reviewed: null -links: [] +links: + - SG-002 + - SG-005 asil: D --- diff --git a/reqs/sys/SYS-003.md b/reqs/sys/SYS-003.md index 9c2ff44..8ef1627 100644 --- a/reqs/sys/SYS-003.md +++ b/reqs/sys/SYS-003.md @@ -5,7 +5,8 @@ header: 'Release auf Fahrer-Anforderung' level: 1.3 normative: true reviewed: null -links: [] +links: + - SG-005 asil: B --- diff --git a/reqs/sys/SYS-004.md b/reqs/sys/SYS-004.md index 0c7ab76..e141f9e 100644 --- a/reqs/sys/SYS-004.md +++ b/reqs/sys/SYS-004.md @@ -5,7 +5,8 @@ header: 'Auto-Apply bei Motor-Aus' level: 1.4 normative: true reviewed: null -links: [] +links: + - SG-001 asil: D --- diff --git a/reqs/sys/SYS-005.md b/reqs/sys/SYS-005.md index f9c5933..401dcb5 100644 --- a/reqs/sys/SYS-005.md +++ b/reqs/sys/SYS-005.md @@ -5,7 +5,9 @@ header: 'Hill-Hold am Berg' level: 1.5 normative: true reviewed: null -links: [] +links: + - SG-002 + - SG-004 asil: D --- diff --git a/reqs/sys/SYS-006.md b/reqs/sys/SYS-006.md index b325dfc..9ec0731 100644 --- a/reqs/sys/SYS-006.md +++ b/reqs/sys/SYS-006.md @@ -5,7 +5,8 @@ header: 'Auto-Release beim Anfahren (Drive-Away-Assist)' level: 1.6 normative: true reviewed: null -links: [] +links: + - SG-004 asil: B --- diff --git a/reqs/sys/SYS-007.md b/reqs/sys/SYS-007.md index 6e2cb6a..c07fc7d 100644 --- a/reqs/sys/SYS-007.md +++ b/reqs/sys/SYS-007.md @@ -5,7 +5,8 @@ header: 'Aktor-Stromueberwachung' level: 1.7 normative: true reviewed: null -links: [] +links: + - SG-003 asil: B --- diff --git a/safety/sg/SG-001.md b/safety/sg/SG-001.md new file mode 100644 index 0000000..8dd557d --- /dev/null +++ b/safety/sg/SG-001.md @@ -0,0 +1,17 @@ +--- +active: true +derived: false +header: 'Kein ungewolltes Loesen der Parkbremse im Stillstand' +level: 1.1 +normative: true +reviewed: null +links: [] +asil: D +--- + +# SG-001: Kein ungewolltes Loesen der Parkbremse im Stillstand + +Die EPB darf sich im Stillstand des Fahrzeugs nicht ungewollt loesen. Abgeleitet aus HARA-Hazards H-01 (ungewolltes Loesen, Parkphase) und H-04 (Klemmkraftverlust im Hold). + +**FTTI:** 5 s (H-01) / 30 s (H-04). +**Safe State:** APPLIED (Klemmkraft halten). diff --git a/safety/sg/SG-002.md b/safety/sg/SG-002.md new file mode 100644 index 0000000..2b2d0b1 --- /dev/null +++ b/safety/sg/SG-002.md @@ -0,0 +1,17 @@ +--- +active: true +derived: false +header: 'Kein ungewolltes Festklemmen waehrend der Fahrt' +level: 1.2 +normative: true +reviewed: null +links: [] +asil: D +--- + +# SG-002: Kein ungewolltes Festklemmen waehrend der Fahrt + +Die EPB darf nicht waehrend der Fahrt ungewollt festklemmen. Abgeleitet aus HARA-Hazard H-02. + +**FTTI:** 100 ms. +**Safe State:** Aktor stop (kein Apply einleiten). diff --git a/safety/sg/SG-003.md b/safety/sg/SG-003.md new file mode 100644 index 0000000..bd4b1ff --- /dev/null +++ b/safety/sg/SG-003.md @@ -0,0 +1,17 @@ +--- +active: true +derived: false +header: 'Schutz gegen Aktor-Ueberlast' +level: 1.3 +normative: true +reviewed: null +links: [] +asil: A +--- + +# SG-003: Schutz gegen Aktor-Ueberlast + +Das System muss Aktor-Motorschaeden durch Ueberstrom verhindern. Abgeleitet aus HARA-Hazard H-05. + +**FTTI:** 100 ms. +**Safe State:** Aktor abschalten, DTC setzen. diff --git a/safety/sg/SG-004.md b/safety/sg/SG-004.md new file mode 100644 index 0000000..2c5f3e9 --- /dev/null +++ b/safety/sg/SG-004.md @@ -0,0 +1,17 @@ +--- +active: true +derived: false +header: 'Zuverlaessige Hill-Hold-Uebergabe' +level: 1.4 +normative: true +reviewed: null +links: [] +asil: C +--- + +# SG-004: Zuverlaessige Hill-Hold-Uebergabe + +Beim Loslassen des Bremspedals an einem Hang muss die EPB die Bremskraft uebernehmen, bevor das Fahrzeug zu rollen beginnt. Abgeleitet aus HARA-Hazard H-06. + +**FTTI:** 500 ms. +**Safe State:** Apply einleiten. diff --git a/safety/sg/SG-005.md b/safety/sg/SG-005.md new file mode 100644 index 0000000..f5c7e3f --- /dev/null +++ b/safety/sg/SG-005.md @@ -0,0 +1,16 @@ +--- +active: true +derived: false +header: 'Reaktion auf Fahreranforderung' +level: 1.5 +normative: true +reviewed: null +links: [] +asil: B +--- + +# SG-005: Reaktion auf Fahreranforderung + +Das System muss in spezifizierter Zeit auf Fahrer-Apply- und Release-Anforderungen reagieren. Abgeleitet aus HARA-Hazards H-03 und H-07. + +**Reaktionszeit:** Apply <= 800 ms, Release <= 1500 ms. diff --git a/src/safety_manager.c b/src/safety_manager.c index 4634984..0598ecb 100644 --- a/src/safety_manager.c +++ b/src/safety_manager.c @@ -1,12 +1,13 @@ /** * @file safety_manager.c - * @brief Safety Manager — Hill-Hold + Auto-Apply Logik. + * @brief Safety Manager — Hill-Hold, Auto-Apply, Drive-Away-Assist. * * @arch SWA-001 - * @reqs SWE-007 SWE-008 SWE-009 SWE-010 + * @reqs SWE-007 SWE-008 SWE-009 SWE-010 SWE-011 SWE-012 * * ASIL: D. Diese Komponente entscheidet, wann der Apply Controller eine - * Apply-Anforderung erhaelt (Hill-Hold-Uebergabe, Auto-Apply bei Motor-Aus). + * Apply- oder Release-Anforderung erhaelt (Hill-Hold-Uebergabe, Auto-Apply + * bei Motor-Aus, Drive-Away-Assist). * Aenderungen erfordern Technical Review mit 2 Approvals. */ #include @@ -17,6 +18,7 @@ typedef struct { SafetyState state; uint16_t ticks_in_state; /* 50ms-Ticks im aktuellen Zustand */ bool apply_requested; + bool release_requested; } SafetyCtx; static SafetyCtx s_ctx; @@ -42,11 +44,30 @@ static bool grade_steep(const SafetyInputs* in) return g > SAFETY_HILLHOLD_GRADE_PCT; } +/** + * @reqs SWE-011 (Anfahrabsicht erkennen) + */ +static bool drive_intent(const SafetyInputs* in) +{ + return (in->gas_pedal_percent > SAFETY_DRIVE_INTENT_GAS_PCT) + && in->gear_in_drive + && in->engine_running; +} + +/** + * @reqs SWE-012 (Sicherheits-Check vor Auto-Release) + */ +static bool drive_away_safety_ok(const SafetyInputs* in) +{ + return in->door_closed && in->seatbelt_fastened; +} + EpbStatus safety_mgr_init(void) { - s_ctx.state = SAFETY_IDLE; - s_ctx.ticks_in_state = 0U; - s_ctx.apply_requested = false; + s_ctx.state = SAFETY_IDLE; + s_ctx.ticks_in_state = 0U; + s_ctx.apply_requested = false; + s_ctx.release_requested = false; return EPB_OK; } @@ -60,8 +81,9 @@ void safety_mgr_step_50ms(const SafetyInputs* in) ++s_ctx.ticks_in_state; } - /* Default: no apply request unless explicitly set below. */ - s_ctx.apply_requested = false; + /* Default: no apply/release request unless explicitly set below. */ + s_ctx.apply_requested = false; + s_ctx.release_requested = false; switch (s_ctx.state) { case SAFETY_IDLE: @@ -75,6 +97,13 @@ void safety_mgr_step_50ms(const SafetyInputs* in) && in->current_state != EPB_STATE_APPLIED && in->current_state != EPB_STATE_APPLYING) { enter(SAFETY_AUTO_APPLY_ARMED); + break; + } + /* @reqs SWE-011 + SWE-012: Drive-Away-Assist */ + if (in->current_state == EPB_STATE_APPLIED + && drive_intent(in) && drive_away_safety_ok(in)) { + s_ctx.release_requested = true; + enter(SAFETY_DRIVE_AWAY); } break; @@ -122,6 +151,18 @@ void safety_mgr_step_50ms(const SafetyInputs* in) } break; + case SAFETY_DRIVE_AWAY: + /* Beendet, wenn die Bremse geloest wurde oder Vorbedingungen nicht mehr ok. */ + if (in->current_state == EPB_STATE_RELEASED + || in->current_state == EPB_STATE_RELEASING) { + enter(SAFETY_IDLE); + } else if (!drive_intent(in) || !drive_away_safety_ok(in)) { + enter(SAFETY_IDLE); + } else { + s_ctx.release_requested = true; + } + break; + default: enter(SAFETY_IDLE); break; @@ -133,6 +174,11 @@ bool safety_mgr_apply_requested(void) return s_ctx.apply_requested; } +bool safety_mgr_release_requested(void) +{ + return s_ctx.release_requested; +} + SafetyState safety_mgr_get_state(void) { return s_ctx.state; diff --git a/src/safety_manager.h b/src/safety_manager.h index 784a69d..f6ea35f 100644 --- a/src/safety_manager.h +++ b/src/safety_manager.h @@ -1,20 +1,23 @@ /** * @file safety_manager.h - * @brief Safety Manager — Hill-Hold + Auto-Apply Logik. + * @brief Safety Manager — Hill-Hold, Auto-Apply, Drive-Away-Assist. * * @arch SWA-001 - * @reqs SWE-007 SWE-008 SWE-009 SWE-010 + * @reqs SWE-007 SWE-008 SWE-009 SWE-010 SWE-011 SWE-012 * * ASIL: D. * * State Machine: - * IDLE --(engine_off & v<0.5)--> AUTO_APPLY_ARMED + * IDLE --(engine_off & v<0.5 & !APPLIED)--> AUTO_APPLY_ARMED * AUTO_APPLY_ARMED --(40 * 50ms = 2s)--> AUTO_APPLY_TRIGGERED * AUTO_APPLY_TRIGGERED --(state==APPLIED)--> IDLE * * IDLE --(grade>5% & v<0.5 & brake)--> HILL_HOLD_ARMED * HILL_HOLD_ARMED --(!brake)--> HILL_HOLD_ACTIVE * HILL_HOLD_ACTIVE --(v>2 km/h | state==APPLIED)--> IDLE + * + * IDLE --(APPLIED & gas>10% & gear_drive & engine & door & belt)--> DRIVE_AWAY + * DRIVE_AWAY --(state==RELEASED|RELEASING)--> IDLE */ #ifndef SAFETY_MANAGER_H #define SAFETY_MANAGER_H @@ -26,7 +29,8 @@ typedef enum { SAFETY_HILL_HOLD_ARMED = 1, SAFETY_HILL_HOLD_ACTIVE = 2, SAFETY_AUTO_APPLY_ARMED = 3, - SAFETY_AUTO_APPLY_TRIGGERED = 4 + SAFETY_AUTO_APPLY_TRIGGERED = 4, + SAFETY_DRIVE_AWAY = 5 } SafetyState; typedef struct { @@ -34,7 +38,12 @@ typedef struct { bool brake_pedal_pressed; float vehicle_speed_kmh; float grade_percent; - EpbState current_state; /* aus Apply Controller */ + EpbState current_state; /* aus Apply Controller */ + /* Drive-Away-Assist Inputs (SWE-011, SWE-012) */ + float gas_pedal_percent; /* 0..100 */ + bool gear_in_drive; /* Vorwaerts oder Rueckwaerts */ + bool door_closed; /* Fahrertuer */ + bool seatbelt_fastened; /* Fahrer-Gurt */ } SafetyInputs; /* Schwellwerte als Konstanten, damit Tests darauf zugreifen koennen. */ @@ -42,10 +51,12 @@ typedef struct { #define SAFETY_STANDSTILL_KMH 0.5f #define SAFETY_RELEASE_KMH 2.0f #define SAFETY_HILLHOLD_GRADE_PCT 5.0f +#define SAFETY_DRIVE_INTENT_GAS_PCT 10.0f /* Gaspedal > 10% = Anfahrabsicht */ EpbStatus safety_mgr_init(void); void safety_mgr_step_50ms(const SafetyInputs* in); bool safety_mgr_apply_requested(void); +bool safety_mgr_release_requested(void); /* Drive-Away-Assist */ SafetyState safety_mgr_get_state(void); #endif /* SAFETY_MANAGER_H */ diff --git a/tests/unit/test_apply_controller.c b/tests/unit/test_apply_controller.c index 9aa573b..1be47eb 100644 --- a/tests/unit/test_apply_controller.c +++ b/tests/unit/test_apply_controller.c @@ -2,7 +2,7 @@ * @file test_apply_controller.c * @brief Unit-Tests fuer den Apply-Controller (ASIL-D Kern). * - * @reqs SWE-001 SWE-002 SWE-003 SWE-004 + * @reqs SWE-001 SWE-002 SWE-003 SWE-004 SWE-005 * @arch SWA-002 */ #include "../unit_test_framework.h" diff --git a/tests/unit/test_safety_manager.c b/tests/unit/test_safety_manager.c index d8b9bba..a301403 100644 --- a/tests/unit/test_safety_manager.c +++ b/tests/unit/test_safety_manager.c @@ -2,7 +2,7 @@ * @file test_safety_manager.c * @brief Unit-Tests fuer den Safety Manager (ASIL-D). * - * @reqs SWE-007 SWE-008 SWE-009 SWE-010 + * @reqs SWE-007 SWE-008 SWE-009 SWE-010 SWE-011 SWE-012 * @arch SWA-001 */ #include "../unit_test_framework.h" @@ -207,6 +207,90 @@ static void test_hillhold_armed_to_idle_if_grade_drops(void) /* ---- Mutually exclusive: nicht in beiden Modi gleichzeitig ---- */ +/* ---- Drive-Away-Assist (SWE-011 + SWE-012) ---- */ + +static SafetyInputs make_applied_at_rest(void) +{ + SafetyInputs in = {0}; + in.engine_running = true; + in.brake_pedal_pressed = false; + in.vehicle_speed_kmh = 0.0f; + in.grade_percent = 0.0f; + in.current_state = EPB_STATE_APPLIED; + in.gas_pedal_percent = 0.0f; + in.gear_in_drive = false; + in.door_closed = true; + in.seatbelt_fastened = true; + return in; +} + +static void test_drive_away_armed_on_intent(void) +{ + TEST_BEGIN("SWE-011 + SWE-012: Anfahrabsicht + Safety -> DRIVE_AWAY + Release-Request"); + (void)safety_mgr_init(); + SafetyInputs in = make_applied_at_rest(); + in.gas_pedal_percent = 25.0f; + in.gear_in_drive = true; + safety_mgr_step_50ms(&in); + ASSERT_EQ(safety_mgr_get_state(), SAFETY_DRIVE_AWAY); + ASSERT_TRUE(safety_mgr_release_requested()); + TEST_END(); +} + +static void test_drive_away_blocked_without_safety(void) +{ + TEST_BEGIN("SWE-012: Tuer offen blockiert Drive-Away"); + (void)safety_mgr_init(); + SafetyInputs in = make_applied_at_rest(); + in.gas_pedal_percent = 25.0f; + in.gear_in_drive = true; + in.door_closed = false; /* Tuer offen */ + safety_mgr_step_50ms(&in); + ASSERT_EQ(safety_mgr_get_state(), SAFETY_IDLE); + ASSERT_TRUE(!safety_mgr_release_requested()); + TEST_END(); +} + +static void test_drive_away_blocked_without_seatbelt(void) +{ + TEST_BEGIN("SWE-012: Gurt nicht angelegt blockiert Drive-Away"); + (void)safety_mgr_init(); + SafetyInputs in = make_applied_at_rest(); + in.gas_pedal_percent = 25.0f; + in.gear_in_drive = true; + in.seatbelt_fastened = false; + safety_mgr_step_50ms(&in); + ASSERT_EQ(safety_mgr_get_state(), SAFETY_IDLE); + TEST_END(); +} + +static void test_drive_away_blocked_below_gas_threshold(void) +{ + TEST_BEGIN("SWE-011: Gas < 10% loest kein Drive-Away aus"); + (void)safety_mgr_init(); + SafetyInputs in = make_applied_at_rest(); + in.gas_pedal_percent = 5.0f; + in.gear_in_drive = true; + safety_mgr_step_50ms(&in); + ASSERT_EQ(safety_mgr_get_state(), SAFETY_IDLE); + TEST_END(); +} + +static void test_drive_away_ends_when_released(void) +{ + TEST_BEGIN("DRIVE_AWAY -> IDLE wenn Apply Controller geloest hat"); + (void)safety_mgr_init(); + SafetyInputs in = make_applied_at_rest(); + in.gas_pedal_percent = 25.0f; + in.gear_in_drive = true; + safety_mgr_step_50ms(&in); /* -> DRIVE_AWAY */ + in.current_state = EPB_STATE_RELEASED; + safety_mgr_step_50ms(&in); + ASSERT_EQ(safety_mgr_get_state(), SAFETY_IDLE); + ASSERT_TRUE(!safety_mgr_release_requested()); + TEST_END(); +} + static void test_already_applied_does_not_arm_auto_apply(void) { TEST_BEGIN("Bereits Applied: kein Auto-Apply Arming"); @@ -235,6 +319,11 @@ int main(void) test_hillhold_active_on_brake_release(); test_hillhold_active_ends_on_vehicle_rolling(); test_hillhold_armed_to_idle_if_grade_drops(); + test_drive_away_armed_on_intent(); + test_drive_away_blocked_without_safety(); + test_drive_away_blocked_without_seatbelt(); + test_drive_away_blocked_below_gas_threshold(); + test_drive_away_ends_when_released(); test_already_applied_does_not_arm_auto_apply(); TEST_SUMMARY(); } diff --git a/tools/generate_doorstop_items.py b/tools/generate_doorstop_items.py index b0dddf8..5f5c88b 100644 --- a/tools/generate_doorstop_items.py +++ b/tools/generate_doorstop_items.py @@ -18,12 +18,72 @@ REPO = Path(__file__).resolve().parent.parent # --------------------------------------------------------------------------- -# System Requirements +# Safety Goals (ISO 26262, abgeleitet aus HARA) +# --------------------------------------------------------------------------- + +SG_GOALS = [ + { + "id": "SG-001", "asil": "D", + "title": "Kein ungewolltes Loesen der Parkbremse im Stillstand", + "text": ( + "Die EPB darf sich im Stillstand des Fahrzeugs nicht ungewollt loesen. " + "Abgeleitet aus HARA-Hazards H-01 (ungewolltes Loesen, Parkphase) und " + "H-04 (Klemmkraftverlust im Hold).\n\n" + "**FTTI:** 5 s (H-01) / 30 s (H-04).\n" + "**Safe State:** APPLIED (Klemmkraft halten)." + ), + }, + { + "id": "SG-002", "asil": "D", + "title": "Kein ungewolltes Festklemmen waehrend der Fahrt", + "text": ( + "Die EPB darf nicht waehrend der Fahrt ungewollt festklemmen. " + "Abgeleitet aus HARA-Hazard H-02.\n\n" + "**FTTI:** 100 ms.\n" + "**Safe State:** Aktor stop (kein Apply einleiten)." + ), + }, + { + "id": "SG-003", "asil": "A", + "title": "Schutz gegen Aktor-Ueberlast", + "text": ( + "Das System muss Aktor-Motorschaeden durch Ueberstrom verhindern. " + "Abgeleitet aus HARA-Hazard H-05.\n\n" + "**FTTI:** 100 ms.\n" + "**Safe State:** Aktor abschalten, DTC setzen." + ), + }, + { + "id": "SG-004", "asil": "C", + "title": "Zuverlaessige Hill-Hold-Uebergabe", + "text": ( + "Beim Loslassen des Bremspedals an einem Hang muss die EPB die " + "Bremskraft uebernehmen, bevor das Fahrzeug zu rollen beginnt. " + "Abgeleitet aus HARA-Hazard H-06.\n\n" + "**FTTI:** 500 ms.\n" + "**Safe State:** Apply einleiten." + ), + }, + { + "id": "SG-005", "asil": "B", + "title": "Reaktion auf Fahreranforderung", + "text": ( + "Das System muss in spezifizierter Zeit auf Fahrer-Apply- und Release-" + "Anforderungen reagieren. Abgeleitet aus HARA-Hazards H-03 und H-07.\n\n" + "**Reaktionszeit:** Apply <= 800 ms, Release <= 1500 ms." + ), + }, +] + + +# --------------------------------------------------------------------------- +# System Requirements (linken nach oben auf SG) # --------------------------------------------------------------------------- SYS_REQS = [ { "id": "SYS-001", "asil": "D", + "links": ["SG-001"], "title": "Halten der Parkbremse im Stillstand", "text": ( "Wenn die Parkbremse aktiviert ist und das Fahrzeug stillsteht, " @@ -35,6 +95,7 @@ SYS_REQS = [ }, { "id": "SYS-002", "asil": "D", + "links": ["SG-002", "SG-005"], "title": "Apply auf Fahrer-Anforderung", "text": ( "Bei Betaetigung des EPB-Schalters in Apply-Richtung muss das " @@ -45,6 +106,7 @@ SYS_REQS = [ }, { "id": "SYS-003", "asil": "B", + "links": ["SG-005"], "title": "Release auf Fahrer-Anforderung", "text": ( "Bei Betaetigung des EPB-Schalters in Release-Richtung muss das " @@ -55,6 +117,7 @@ SYS_REQS = [ }, { "id": "SYS-004", "asil": "D", + "links": ["SG-001"], "title": "Auto-Apply bei Motor-Aus", "text": ( "Wenn der Motor ausgeschaltet wird und das Fahrzeug stillsteht " @@ -65,6 +128,7 @@ SYS_REQS = [ }, { "id": "SYS-005", "asil": "D", + "links": ["SG-002", "SG-004"], "title": "Hill-Hold am Berg", "text": ( "Bei aktivem Hill-Hold (Fahrzeug steht am Hang mit Neigung > 5%, " @@ -75,6 +139,7 @@ SYS_REQS = [ }, { "id": "SYS-006", "asil": "B", + "links": ["SG-004"], "title": "Auto-Release beim Anfahren (Drive-Away-Assist)", "text": ( "Wenn die Parkbremse aktiv ist und der Fahrer Anfahrabsicht zeigt " @@ -86,6 +151,7 @@ SYS_REQS = [ }, { "id": "SYS-007", "asil": "B", + "links": ["SG-003"], "title": "Aktor-Stromueberwachung", "text": ( "Das System muss den Motorstrom jedes Aktors mit mindestens 1 kHz " @@ -795,9 +861,10 @@ def write_items(items, target_dir: Path, with_links=True): def main(): - write_items(SYS_REQS, REPO / "reqs" / "sys") - write_items(SWE_REQS, REPO / "reqs" / "swe") - write_items(SA_ELEMENTS, REPO / "arch" / "sys") + write_items(SG_GOALS, REPO / "safety" / "sg") + write_items(SYS_REQS, REPO / "reqs" / "sys") + write_items(SWE_REQS, REPO / "reqs" / "swe") + write_items(SA_ELEMENTS, REPO / "arch" / "sys") write_items(SWA_ELEMENTS, REPO / "arch" / "swe") print("\nTotal: {} reqs/arch items.".format( len(SYS_REQS) + len(SWE_REQS) + len(SA_ELEMENTS) + len(SWA_ELEMENTS) diff --git a/tools/traceability.py b/tools/traceability.py index 7c22a0d..90f457c 100644 --- a/tools/traceability.py +++ b/tools/traceability.py @@ -2,11 +2,12 @@ """ Traceability-Werkzeug fuer demo-epb. -Liest alle Markdown-Items in reqs/ und arch/ ein, validiert Links bidirektional -und erzeugt eine HTML-Traceability-Matrix. +Liest Markdown-Items aus safety/sg, reqs/sys, reqs/swe, arch/sys, arch/swe und +verifiziert die Traceability-Kette: -Doorstop-kompatibles Format (YAML-Frontmatter + Markdown-Body), aber ohne -Doorstop-Dependency — bleibt portabel. + SG <-- SYS <-- SA + <-- SWE <-- SWA <-- Code (@arch) + <-- Tests (@reqs) Subcommands: check Validiert Konsistenz, exit 1 bei Fehlern @@ -28,25 +29,42 @@ from pathlib import Path REPO = Path(__file__).resolve().parent.parent SOURCES = [ + ("SG", "safety/sg", "Safety Goals"), ("SYS", "reqs/sys", "System Requirements"), ("SWE", "reqs/swe", "Software Requirements"), ("SA", "arch/sys", "System Architecture"), ("SWA", "arch/swe", "Software Architecture"), ] -# Welche Quellen verlinken auf welche? -# (key) -> (target_prefix) : Items mit key linken auf Items mit target_prefix +# Forward: items of prefix SHOULD link to prefix EXPECTED_LINKS = { "SA": ["SYS"], "SWE": ["SYS"], "SWA": ["SWE"], + # SYS optionally links to SG — checked separately, only for safety-relevant SYS } -# Reverse: welche Quellen MUESSEN von welchen Quellen referenziert werden? -# (target) -> [prefix that should link to target] (coverage check) +# Reverse coverage: each item of must be referenced by all items in list COVERAGE = { - "SYS": ["SA", "SWE"], # jede SYS-Req muss durch SA und SWE abgedeckt sein - "SWE": ["SWA"], # jede SWE-Req muss durch SWA abgedeckt sein + "SG": ["SYS"], # each SG must be detailed by at least one SYS + "SYS": ["SA", "SWE"], # each SYS must be covered by SA + SWE + "SWE": ["SWA"], # each SWE must be implemented by SWA +} + +# Components that are implemented in src/ (have a .c file) +IMPLEMENTED_SWA = { + "SWA-001": "src/safety_manager.c", + "SWA-002": "src/apply_controller.c", + "SWA-003": "src/actuator_driver.c", + "SWA-006": "src/switch_debouncer.c", +} + +# Tests we ship — map test file → SWA it covers +IMPLEMENTED_TESTS = { + "test_safety_manager.c": "SWA-001", + "test_apply_controller.c": "SWA-002", + "test_actuator_driver.c": "SWA-003", + "test_switch_debouncer.c": "SWA-006", } @@ -61,7 +79,6 @@ class Item: FRONTMATTER_RE = re.compile(r"^---\s*\n(.*?)\n---\s*\n", re.DOTALL) -LINKS_RE = re.compile(r"^\s*-\s+([A-Z]+-\d+)", re.MULTILINE) def parse_item(path: Path, prefix: str) -> Item | None: @@ -71,7 +88,6 @@ def parse_item(path: Path, prefix: str) -> Item | None: return None fm = m.group(1) - # Crude YAML parsing — we only need a few fields. def field_value(name: str) -> str | None: rx = re.search(rf"^{name}:\s*(.*?)$", fm, re.MULTILINE) return rx.group(1).strip().strip("'\"") if rx else None @@ -79,7 +95,6 @@ def parse_item(path: Path, prefix: str) -> Item | None: asil = field_value("asil") or "?" title = field_value("header") or path.stem - # links: collect IDs from the `links:` block. links: list[str] = [] in_links = False for line in fm.splitlines(): @@ -112,16 +127,76 @@ def collect_all() -> dict[str, Item]: return items +# --------------------------------------------------------------------------- +# Code / Test traceability +# --------------------------------------------------------------------------- + +ARCH_TAG_RE = re.compile(r"@arch\s+([A-Z]+-\d+(?:\s+[A-Z]+-\d+)*)") +REQS_TAG_RE = re.compile(r"@reqs\s+([A-Z]+-\d+(?:\s+[A-Z]+-\d+)*)") + + +def extract_tags(path: Path) -> tuple[list[str], list[str]]: + """Extract @arch and @reqs tag IDs from the file header (first ~400 chars).""" + if not path.exists(): + return [], [] + head = path.read_text()[:600] + arch_ids: list[str] = [] + reqs_ids: list[str] = [] + for m in ARCH_TAG_RE.finditer(head): + arch_ids.extend(re.findall(r"[A-Z]+-\d+", m.group(1))) + for m in REQS_TAG_RE.finditer(head): + reqs_ids.extend(re.findall(r"[A-Z]+-\d+", m.group(1))) + return arch_ids, reqs_ids + + +def check_code_test_mapping(items: dict[str, Item]) -> list[str]: + """Check that: + - Each implemented .c file's @arch matches its SWA-id (per IMPLEMENTED_SWA) + - Each test's @reqs covers all SWE that the corresponding SWA implements + """ + errors: list[str] = [] + + for swa_id, src_rel in IMPLEMENTED_SWA.items(): + src = REPO / src_rel + arch_tags, _ = extract_tags(src) + if swa_id not in arch_tags: + errors.append(f"{src_rel}: header @arch enthaelt {swa_id} nicht " + f"(gefunden: {arch_tags or '—'})") + + # For each test, verify @reqs covers the SWE that the corresponding SWA links to + for test_file, swa_id in IMPLEMENTED_TESTS.items(): + test_path = REPO / "tests" / "unit" / test_file + _, reqs_in_test = extract_tags(test_path) + if not reqs_in_test: + errors.append(f"tests/unit/{test_file}: kein @reqs Tag im Header") + continue + swa = items.get(swa_id) + if swa is None: + errors.append(f"tests/unit/{test_file}: referenziertes " + f"{swa_id} nicht gefunden") + continue + swa_swe = set(swa.links) + test_swe = set(reqs_in_test) + missing = swa_swe - test_swe + if missing: + errors.append(f"tests/unit/{test_file}: deckt nicht alle SWE " + f"der {swa_id} ab — fehlend: {sorted(missing)}") + + return errors + + +# --------------------------------------------------------------------------- +# Commands +# --------------------------------------------------------------------------- + def cmd_check(items: dict[str, Item]) -> int: errors: list[str] = [] - # 1. Each link target must exist for it in items.values(): for link in it.links: if link not in items: errors.append(f"{it.id} links to non-existent {link}") - # 2. Forward expectation: items of certain prefixes must link to others for it in items.values(): expected_prefixes = EXPECTED_LINKS.get(it.prefix, []) if not expected_prefixes: @@ -133,7 +208,6 @@ def cmd_check(items: dict[str, Item]) -> int: f"{it.id} ({it.prefix}) has no link to a {ep}-* item" ) - # 3. Coverage: each item of certain prefix must be referenced by certain types incoming: dict[str, set[str]] = {iid: set() for iid in items} for it in items.values(): for link in it.links: @@ -144,22 +218,30 @@ def cmd_check(items: dict[str, Item]) -> int: required = COVERAGE.get(it.prefix, []) for rp in required: if rp not in incoming[it.id]: + # Exclude QM-level SYS reqs from SG coverage check + if rp == "SYS" and it.prefix == "SG": + continue errors.append( f"{it.id} ({it.prefix}) is not referenced by any {rp}-* item" ) - print(f"\nItems found: {len(items)}") - print(f" SYS: {sum(1 for i in items.values() if i.prefix == 'SYS')}") - print(f" SWE: {sum(1 for i in items.values() if i.prefix == 'SWE')}") - print(f" SA: {sum(1 for i in items.values() if i.prefix == 'SA')}") - print(f" SWA: {sum(1 for i in items.values() if i.prefix == 'SWA')}") + # Code + Test mapping + errors.extend(check_code_test_mapping(items)) + + counts = {p: sum(1 for i in items.values() if i.prefix == p) + for p, _, _ in SOURCES} + print(f"\nItems found: {sum(counts.values())}") + for p, _, _ in SOURCES: + print(f" {p:4} {counts[p]:3}") + print(f"\nCode mappings: {len(IMPLEMENTED_SWA)} implemented SWA") + print(f"Test mappings: {len(IMPLEMENTED_TESTS)} test files") print() if errors: print(f"FAIL: {len(errors)} traceability error(s):") for e in errors: print(f" - {e}") return 1 - print("OK — Traceability vollstaendig.") + print("OK — Traceability vollstaendig (SG → SYS → SA, SWE → SWA → Code+Test).") return 0 @@ -176,7 +258,7 @@ def asil_color(asil: str) -> str: def cmd_publish(items: dict[str, Item], out_dir: Path) -> int: out_dir.mkdir(parents=True, exist_ok=True) - # Build forward and reverse maps + # Build reverse map: who links to me? children: dict[str, list[str]] = {iid: [] for iid in items} for it in items.values(): for link in it.links: @@ -184,15 +266,35 @@ def cmd_publish(items: dict[str, Item], out_dir: Path) -> int: children[link].append(it.id) rows = [] - sys_items = [i for i in items.values() if i.prefix == "SYS"] - for sys in sorted(sys_items, key=lambda i: i.id): - sas = [c for c in children[sys.id] if items[c].prefix == "SA"] - swes = [c for c in children[sys.id] if items[c].prefix == "SWE"] - swas = sorted(set(c for s in swes for c in children[s] - if items[c].prefix == "SWA")) - rows.append({ - "sys": sys, "sa": sas, "swe": swes, "swa": swas - }) + sgs = [i for i in items.values() if i.prefix == "SG"] + qm_sys = [i for i in items.values() if i.prefix == "SYS" and not i.links] + # For each SG, build a row + for sg in sorted(sgs, key=lambda i: i.id): + sys_items = [c for c in children[sg.id] if items[c].prefix == "SYS"] + for s in sorted(sys_items): + sys_it = items[s] + sas = [c for c in children[sys_it.id] if items[c].prefix == "SA"] + swes = [c for c in children[sys_it.id] if items[c].prefix == "SWE"] + swas = sorted({c for sw in swes for c in children[sw] + if items[c].prefix == "SWA"}) + code = sorted({swa for swa in swas if swa in IMPLEMENTED_SWA}) + tests = sorted({f for f, swa in IMPLEMENTED_TESTS.items() + if swa in swas}) + rows.append({"sg": sg, "sys": sys_it, "sa": sas, "swe": swes, + "swa": swas, "code": code, "tests": tests}) + + # Also include QM-level SYS (not linked to SG) as separate section + for sys_it in sorted(qm_sys, key=lambda i: i.id): + sas = [c for c in children[sys_it.id] if items[c].prefix == "SA"] + swes = [c for c in children[sys_it.id] if items[c].prefix == "SWE"] + swas = sorted({c for sw in swes for c in children[sw] + if items[c].prefix == "SWA"}) + code = sorted({swa for swa in swas if swa in IMPLEMENTED_SWA}) + tests = sorted({f for f, swa in IMPLEMENTED_TESTS.items() if swa in swas}) + rows.append({"sg": None, "sys": sys_it, "sa": sas, "swe": swes, + "swa": swas, "code": code, "tests": tests}) + + counts = {p: sum(1 for i in items.values() if i.prefix == p) for p, _, _ in SOURCES} # HTML parts = [ @@ -201,60 +303,116 @@ def cmd_publish(items: dict[str, Item], out_dir: Path) -> int: "demo-epb — Traceability Matrix", "", "

demo-epb — Traceability Matrix

", - f"

Generiert aus {sum(1 for _ in items)} Items " - f"(SYS: {len([i for i in items.values() if i.prefix=='SYS'])}, " - f"SWE: {len([i for i in items.values() if i.prefix=='SWE'])}, " - f"SA: {len([i for i in items.values() if i.prefix=='SA'])}, " - f"SWA: {len([i for i in items.values() if i.prefix=='SWA'])}).

", - "", - "" - "", + "

Vollstaendige Kette: SG → SYS → SA, SWE → SWA → Code (@arch) + Test (@reqs)

", + "

", ] + for p, _, label in SOURCES: + parts.append(f"{p}: {counts[p]}   ") + parts.append(f"Code-Files: {len(IMPLEMENTED_SWA)}   ") + parts.append(f"Test-Files: {len(IMPLEMENTED_TESTS)}") + parts.append("

") - def cell(ids: list[str]) -> str: + parts.append("
System-RequirementSystem-Arch (SA)Software-Req (SWE)Software-Arch (SWA)
") + parts.append( + "" + "" + "" + ) + + def cell_items(ids: list[str]) -> str: if not ids: - return "" + return "" bits = [] for i in ids: it = items[i] c = asil_color(it.asil) bits.append( f"
{html.escape(i)} " - f"{html.escape(it.asil)}
" + f"" + f"{html.escape(it.asil)}" f"
{html.escape(it.title)}
" ) return "" + def cell_item(it: Item | None) -> str: + if it is None: + return "" + c = asil_color(it.asil) + return (f"") + + def cell_files(files: list[str], prefix: str = "") -> str: + if not files: + return "" + return "" + for r in rows: - sys = r["sys"] - c = asil_color(sys.asil) - first = (f"") - parts.append("" + first + cell(r["sa"]) + cell(r["swe"]) + cell(r["swa"]) + "") + parts.append("") + parts.append(cell_item(r["sg"])) + parts.append(cell_item(r["sys"])) + parts.append(cell_items(r["sa"])) + parts.append(cell_items(r["swe"])) + parts.append(cell_items(r["swa"])) + parts.append(cell_files([IMPLEMENTED_SWA.get(s, "") for s in r["code"] + if IMPLEMENTED_SWA.get(s)])) + parts.append(cell_files(r["tests"], "tests/unit/")) + parts.append("") - parts.append("
Safety GoalSystem-RequirementSystem-ArchSoftware-ReqSoftware-ArchCodeTest
" + "".join(bits) + "
{html.escape(it.id)} " + f"" + f"{html.escape(it.asil)}
" + f"
{html.escape(it.title)}
" + "".join( + f"
{html.escape(prefix + f)}
" + for f in files + ) + "
{html.escape(sys.id)} " - f"{html.escape(sys.asil)}
" - f"
{html.escape(sys.title)}
") + parts.append("") + # Code/Test details + parts.append("

Code → Architektur

") + parts.append("") + for swa_id, src_rel in IMPLEMENTED_SWA.items(): + arch, reqs = extract_tags(REPO / src_rel) + parts.append( + f"" + f"" + f"" + ) + parts.append("
Datei@arch@reqs
{html.escape(src_rel)}{' '.join(arch)}{' '.join(reqs)}
") + + parts.append("

Test → Anforderungen

") + parts.append("") + for test_file, swa_id in IMPLEMENTED_TESTS.items(): + _, reqs = extract_tags(REPO / "tests" / "unit" / test_file) + parts.append( + f"" + f"" + f"" + ) + parts.append("
Test-DateiDecklt SWA@reqs
tests/unit/{html.escape(test_file)}{swa_id}{' '.join(reqs)}
") + + parts.append("") (out_dir / "index.html").write_text("\n".join(parts)) - # JSON for machine consumption + # JSON matrix = [] for r in rows: matrix.append({ + "sg": {"id": r["sg"].id, "asil": r["sg"].asil} if r["sg"] else None, "sys": {"id": r["sys"].id, "asil": r["sys"].asil, "title": r["sys"].title}, "sa": [{"id": i, "asil": items[i].asil} for i in r["sa"]], "swe": [{"id": i, "asil": items[i].asil} for i in r["swe"]], "swa": [{"id": i, "asil": items[i].asil} for i in r["swa"]], + "code": [IMPLEMENTED_SWA[s] for s in r["code"] if s in IMPLEMENTED_SWA], + "tests": [f"tests/unit/{f}" for f in r["tests"]], }) (out_dir / "matrix.json").write_text(json.dumps(matrix, indent=2))