Safe API Framework
Layered API framework for safety-related applications (ERTMS RBC reference targeting CENELEC EN 50128 SIL 4)
Loading...
Searching...
No Matches
Architecture Diagrams with PlantUML & Doxygen

This guide explains how to create and embed structural and behavioral diagrams in the architecture documentation using PlantUML.


Overview

PlantUML is a UML diagram generation tool that converts text descriptions into visual diagrams. Combined with Doxygen, it enables:

  • ✅ Structural diagrams (component, class, deployment)
  • ✅ Behavioral diagrams (sequence, state machine, activity)
  • ✅ Embedded in code comments and markdown
  • ✅ Auto-generated from source control
  • ✅ Version-tracked alongside code

Setup

1. Install PlantUML

# macOS
brew install plantuml
# Linux
apt-get install plantuml
# Verify
plantuml -version

2. Configure Doxygen

Update Doxyfile to enable PlantUML:

PLANTUML_JAR_PATH = /usr/local/bin/plantuml
PLANTUML_INCLUDE_PATH = .
HAVE_DOT = YES
DOT_PATH = /usr/local/bin

3. Generate Docs

doxygen Doxyfile
open html/index.html

Diagram Types & Examples

1. Component Diagram (Structural)

Shows module dependencies and interactions.

File: docs/architecture/diagrams/component-diagram.puml

@startuml component-sapiframework
skinparam componentStyle uml2
package "safeAPIFramework" {
component [status] as status_mod
component [types] as types_mod
component [buffer] as buffer_mod
component [cast] as cast_mod
component [safestate] as safestate_mod
component [string] as string_mod
component [timer] as timer_mod
component [nvm] as nvm_mod
component [memory] as memory_mod
component [task] as task_mod
component [ipc] as ipc_mod
component [log] as log_mod
component [reboot] as reboot_mod
component [redundancy] as redundancy_mod
component [watchdog] as watchdog_mod
}
package "Application Layer" {
component [RBC Logic] as rbc_app
}
package "OS Abstraction Layer (OAL)" {
component [POSIX Backend] as posix_backend
component [QNX Backend] as qnx_backend
}
' Dependencies
status_mod --> types_mod : uses
buffer_mod --> types_mod : uses
cast_mod --> types_mod : uses
string_mod --> buffer_mod : uses
safestate_mod --> log_mod : logs to
timer_mod --> status_mod : returns
nvm_mod --> status_mod : returns
memory_mod --> status_mod : returns
task_mod --> status_mod : returns
ipc_mod --> status_mod : returns
log_mod --> status_mod : returns
reboot_mod --> status_mod : returns
redundancy_mod --> ipc_mod : uses
redundancy_mod --> status_mod : returns
watchdog_mod --> timer_mod : uses
watchdog_mod --> safestate_mod : triggers
rbc_app --> redundancy_mod : uses
rbc_app --> watchdog_mod : uses
rbc_app --> ipc_mod : uses
redundancy_mod --> posix_backend : backend
redundancy_mod --> qnx_backend : backend
@enduml

Embed in Doxygen:


2. Sequence Diagram (Behavioral - IPC)

Shows message flow between components.

File: docs/architecture/diagrams/sequence-ipc-request-reply.puml

@startuml sequence-ipc-rr
participant "Train Controller\n(Client)" as client
participant "IPC Layer" as ipc
participant "Signal Database\n(Server)" as server
client ->> ipc: sapi_ipc_send_request(query, 5s timeout)
note right of client: "What is signal at position 100m?"
ipc ->> server: receive_request()
note right of ipc: Route to server queue
server ->> server: process_signal_query()
note right of server: Compute signal state
server ->> ipc: sapi_ipc_send_reply(reply)
note right of server: "Signal is RED, speed limit 40km/h"
ipc ->> client: reply received
note right of client: Client unblocks with reply
client ->> client: apply_brakes()
alt Timeout Scenario
note over ipc: No reply within 5s
ipc ->> client: TIMEOUT error
client ->> client: trigger_safestate()
end
@enduml

3. Sequence Diagram (Behavioral - Redundancy)

Shows 2oo3 voting flow.

File: docs/architecture/diagrams/sequence-2oo3-voting.puml

@startuml sequence-2oo3-voting
participant "Site A" as siteA
participant "Voting Engine" as voter
participant "Site B" as siteB
participant "Site C" as siteC
participant "Output" as output
siteA ->> siteA: process_signal()
note right of siteA: Signal = GREEN
siteB ->> siteB: process_signal()
note right of siteB: Signal = GREEN
siteC ->> siteC: process_signal()
note right of siteC: Signal = RED (disagreement!)
siteA ->> voter: stage_output(GREEN)
siteB ->> voter: stage_output(GREEN)
siteC ->> voter: stage_output(RED)
voter ->> voter: checkpoint_barrier(200ms)
note right of voter: All sites synchronized
voter ->> voter: sync_outputs()
note right of voter: Exchange and verify
voter ->> voter: voting_2oo3()
note right of voter: 2 GREEN vs 1 RED\n→ Majority: GREEN
voter ->> voter: commit_output(GREEN)
note right of voter: All nodes commit
voter ->> output: send_output(GREEN)
note over siteC: Site C flagged as faulty\n(health monitoring)
@enduml

4. State Machine Diagram (Behavioral)

Shows watchdog state transitions.

File: docs/architecture/diagrams/statemachine-watchdog.puml

@startuml statemachine-watchdog
[*] --> CREATED
CREATED --> STOPPED: sapi_watchdog_create()
STOPPED --> RUNNING: sapi_watchdog_start()
RUNNING --> RUNNING: sapi_watchdog_kick()\n[countdown reset]
RUNNING --> TIMEOUT: countdown == 0ms
note right of TIMEOUT: Watchdog fires!
TIMEOUT --> RECOVER: apply recovery action
RECOVER --> SAFE_STATE: action=SAFESTATE
RECOVER --> REBOOT: action=REBOOT
RECOVER --> FAILOVER: action=FAILOVER
RECOVER --> LOG_ONLY: action=LOG
SAFE_STATE --> [*]
REBOOT --> [*]
FAILOVER --> [*]
LOG_ONLY --> RUNNING: optional restart
RUNNING --> STOPPED: sapi_watchdog_stop()
STOPPED --> [*]: sapi_watchdog_destroy()
@enduml

5. Deployment Diagram (Structural)

Shows how SAPI is deployed on hardware.

File: docs/architecture/diagrams/deployment-2oo3-cluster.puml

@startuml deployment-2oo3
artifact "safeAPIFramework" as sapi_lib
node "Site A (CPU 1)" as siteA {
component [RBC Logic] as rbcA
component [IPC] as ipcA
component [Watchdog] as wdA
component [POSIX OAL] as oalA
database "Timer\nNVM\nTask" as osal_a
}
node "Site B (CPU 2)" as siteB {
component [RBC Logic] as rbcB
component [IPC] as ipcB
component [Watchdog] as wdB
component [POSIX OAL] as oalB
database "Timer\nNVM\nTask" as osal_b
}
node "Site C (CPU 3)" as siteC {
component [RBC Logic] as rbcC
component [IPC] as ipcC
component [Watchdog] as wdC
component [POSIX OAL] as oalC
database "Timer\nNVM\nTask" as osal_c
}
node "Voting Engine (Central)" as voter {
component [Redundancy Manager] as voting
component [Checkpoint Barrier] as ckpt
}
sapi_lib --> siteA
sapi_lib --> siteB
sapi_lib --> siteC
sapi_lib --> voter
ipcA -right-> ipcB: network
ipcB -right-> ipcC: network
ipcA -down-> voter: voting results
ipcB -down-> voter: voting results
ipcC -down-> voter: voting results
wdA -down-> osal_a: heartbeat
wdB -down-> osal_b: heartbeat
wdC -down-> osal_c: heartbeat
@enduml

6. Activity Diagram (Behavioral - Checkpoint Flow)

Shows checkpoint synchronization workflow.

File: docs/architecture/diagrams/activity-checkpoint-barrier.puml

@startuml activity-checkpoint
start
:All nodes process input
independently until checkpoint;
:Node A reaches checkpoint|
:Node B reaches checkpoint|
:Node C slow, waiting...;
if (Timeout < 200ms?) then (YES)
:Node C reaches checkpoint;
:All nodes synchronized ✓;
else (NO - TIMEOUT)
:Node C faulty!;
:Isolate Node C;
:Continue as 2oo2 (A+B only);
endif
:All nodes exchange data;
:Perform voting/comparison;
if (Consensus reached?) then (YES)
:Commit output;
:Send output;
:Success ✓;
else (NO - DISAGREEMENT)
:Abort output;
:Trigger safe-state;
:Failure logged;
endif
stop
@enduml

Embedding Diagrams in Doxygen Comments

In Header Files

In Markdown Files

# Module Architecture
## Structural View

puml

## Behavioral View

puml

In ADR Documents

# ADR-007: Per-Feature Modular Architecture
## Decision
Each feature is an independent module with clear boundaries.
## Diagram
\`\`\`puml
@startuml
package "safeAPIFramework" {
[Module A] as modA
[Module B] as modB
}
modA --> modB: optional dependency
\`\`\`
## Rationale
- Enables independent testing
- Reduces verification scope
- Facilitates reuse

Diagram Locations

Organize diagrams in the repo:

docs/architecture/
├── diagrams/
│ ├── component-diagram.puml
│ ├── sequence-ipc-request-reply.puml
│ ├── sequence-2oo3-voting.puml
│ ├── statemachine-watchdog.puml
│ ├── deployment-2oo3-cluster.puml
│ ├── activity-checkpoint-barrier.puml
│ └── README.md (diagram index)
├── ADR-001-os-abstraction-layer.md
├── ADR-005-oal-backend-registration.md
└── ...

Build Diagrams Locally

# Generate PNG from single diagram
plantuml docs/architecture/diagrams/component-diagram.puml
# Generate all diagrams in directory
plantuml docs/architecture/diagrams/*.puml
# Generate as SVG (vector, better quality)
plantuml -tsvg docs/architecture/diagrams/*.puml
# Verify syntax without generating
plantuml -checkonly docs/architecture/diagrams/component-diagram.puml

Doxygen Configuration

Add to Doxyfile:

# PlantUML settings
PLANTUML_JAR_PATH = /usr/local/bin/plantuml
PLANTUML_INCLUDE_PATH = ./docs/architecture/diagrams
PLANTUML_CFG_FILE = ./plantuml.cfg
# Diagram generation
HAVE_DOT = YES
DOT_PATH = /usr/local/bin
MSCGEN_PATH = /usr/local/bin
# Image output format (PNG or SVG)
DOT_IMAGE_FORMAT = svg
SVG_DYNAMIC_DEPTH = 0
# Generate diagrams in documentation
GENERATE_LATEX = YES
LATEX_BATCHMODE = YES

CI/CD Integration

Add to GitHub Actions workflow:

name: Build Documentation
on: [push, pull_request]
jobs:
docs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install dependencies
run: |
sudo apt-get install -y doxygen graphviz plantuml
- name: Build diagrams
run: |
plantuml docs/architecture/diagrams/*.puml
- name: Build documentation
run: doxygen Doxyfile
- name: Deploy to GitHub Pages
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./html

Best Practices

1. Diagram Scope

Keep diagrams focused:

  • ❌ Don't show all modules in one diagram
  • ✅ Show one architectural aspect per diagram
  • ✅ Use multiple diagrams for complex systems

2. Consistency

Use consistent notation:

skinparam componentStyle uml2
skinparam monochrome true

3. Traceability

Link diagrams to code:

Component → sapi_*.h header file → Implementation
Sequence → Test case → Documented behavior
State Machine → State enum → Code logic

4. Version Control

Commit PlantUML source, not generated images:

✅ docs/architecture/diagrams/*.puml (source)
❌ docs/architecture/diagrams/*.png (generated)

Common Diagram Patterns for Safety-Critical Systems

Pattern 1: Fault Tolerance

@startuml fault-tolerance
participant "Primary" as p
participant "Voter" as v
participant "Backup" as b
p ->> v: result_A
b ->> v: result_B
v ->> v: compare(A, B)
alt Agreement
v ->> output: send(A)
else Disagreement
v ->> safestate: trigger()
end
@enduml

Pattern 2: Health Monitoring

@startuml health-monitoring
participant "Task" as t
participant "Watchdog" as w
t ->> w: kick()
w ->> w: reset_countdown()
t ->> t: do_work()
t ->> w: kick()
alt Timeout
w ->> w: timeout_fired()
w ->> recovery: take_action()
end
@enduml

Pattern 3: Layered Architecture

@startuml layered-arch
package "Application Layer" {
[RBC Logic]
}
package "Framework Layer (SAPI)" {
[IPC] [Timer] [Watchdog]
}
package "OAL Backend Layer" {
[POSIX] [QNX]
}
package "OS/Hardware Layer" {
[Linux] [RTOS]
}
@enduml

References