Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/design/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ This section contains design documentation for the ros2_medkit project packages.
.. toctree::
:maxdepth: 1

ros2_medkit_fault_manager/index
ros2_medkit_gateway/index

1 change: 1 addition & 0 deletions docs/design/ros2_medkit_fault_manager
116 changes: 116 additions & 0 deletions src/ros2_medkit_fault_manager/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# Copyright 2025 mfaferek93
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

cmake_minimum_required(VERSION 3.8)
project(ros2_medkit_fault_manager)

set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic -Wshadow -Wconversion)
endif()

# Code coverage option
option(ENABLE_COVERAGE "Enable code coverage reporting" OFF)
if(ENABLE_COVERAGE)
message(STATUS "Code coverage enabled")
add_compile_options(--coverage -O0 -g)
add_link_options(--coverage)
endif()

find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(ros2_medkit_msgs REQUIRED)

# Library target (shared between executable and tests)
add_library(fault_manager_lib STATIC
src/fault_manager_node.cpp
src/fault_storage.cpp
)

target_include_directories(fault_manager_lib PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
)

ament_target_dependencies(fault_manager_lib
rclcpp
ros2_medkit_msgs
)

if(ENABLE_COVERAGE)
target_compile_options(fault_manager_lib PRIVATE --coverage -O0 -g)
target_link_options(fault_manager_lib PRIVATE --coverage)
endif()

# Executable target
add_executable(fault_manager_node src/main.cpp)
target_link_libraries(fault_manager_node fault_manager_lib)

# Apply coverage flags to executable
if(ENABLE_COVERAGE)
target_compile_options(fault_manager_node PRIVATE --coverage -O0 -g)
target_link_options(fault_manager_node PRIVATE --coverage)
endif()

# Install targets
install(TARGETS fault_manager_node
DESTINATION lib/${PROJECT_NAME}
)

install(DIRECTORY include/
DESTINATION include
)

install(DIRECTORY launch
DESTINATION share/${PROJECT_NAME}
)

# Testing
if(BUILD_TESTING)
find_package(ament_lint_auto REQUIRED)
find_package(ament_cmake_gtest REQUIRED)
find_package(launch_testing_ament_cmake REQUIRED)

set(ament_cmake_clang_format_CONFIG_FILE "${CMAKE_CURRENT_SOURCE_DIR}/../../.clang-format")
set(ament_cmake_clang_tidy_CONFIG_FILE "${CMAKE_CURRENT_SOURCE_DIR}/../../.clang-tidy")
list(APPEND AMENT_LINT_AUTO_EXCLUDE ament_cmake_uncrustify ament_cmake_cpplint)
ament_lint_auto_find_test_dependencies()

# Unit tests
ament_add_gtest(test_fault_manager test/test_fault_manager.cpp)
target_link_libraries(test_fault_manager fault_manager_lib)
ament_target_dependencies(test_fault_manager rclcpp ros2_medkit_msgs)

# Apply coverage flags to test target
if(ENABLE_COVERAGE)
target_compile_options(test_fault_manager PRIVATE --coverage -O0 -g)
target_link_options(test_fault_manager PRIVATE --coverage)
endif()

# Integration tests
install(DIRECTORY test
DESTINATION share/${PROJECT_NAME}
)

add_launch_test(
test/test_integration.test.py
TARGET test_integration
TIMEOUT 60
)
endif()

ament_package()
66 changes: 66 additions & 0 deletions src/ros2_medkit_fault_manager/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# ros2_medkit_fault_manager

Central fault manager node for the ros2_medkit fault management system.

## Overview

The FaultManager node provides a central point for fault aggregation and lifecycle management.
It receives fault reports from multiple sources, aggregates them by `fault_code`, and provides
query and clearing interfaces.

## Services

| Service | Type | Description |
|---------|------|-------------|
| `~/report_fault` | `ros2_medkit_msgs/srv/ReportFault` | Report a fault occurrence |
| `~/get_faults` | `ros2_medkit_msgs/srv/GetFaults` | Query faults with filtering |
| `~/clear_fault` | `ros2_medkit_msgs/srv/ClearFault` | Clear/acknowledge a fault |

## Features

- **Multi-source aggregation**: Same `fault_code` from different sources creates a single fault
- **Occurrence tracking**: Counts total reports and tracks all reporting sources
- **Severity escalation**: Fault severity is updated if a higher severity is reported
- **Status lifecycle**: PENDING → CONFIRMED → CLEARED (automatic status transitions in Issue #6)

## Usage

### Launch

```bash
ros2 launch ros2_medkit_fault_manager fault_manager.launch.py
```

### Manual Testing

```bash
# Report a fault
ros2 service call /fault_manager/report_fault ros2_medkit_msgs/srv/ReportFault \
"{fault_code: 'MOTOR_OVERHEAT', severity: 2, description: 'Motor temp exceeded', source_id: '/motor_node'}"

# Get all faults (including PENDING)
ros2 service call /fault_manager/get_faults ros2_medkit_msgs/srv/GetFaults \
"{filter_by_severity: false, severity: 0, statuses: ['PENDING', 'CONFIRMED']}"

# Clear a fault
ros2 service call /fault_manager/clear_fault ros2_medkit_msgs/srv/ClearFault \
"{fault_code: 'MOTOR_OVERHEAT'}"
```

## Building

```bash
colcon build --packages-select ros2_medkit_fault_manager
source install/setup.bash
```

## Testing

```bash
colcon test --packages-select ros2_medkit_fault_manager
colcon test-result --verbose
```

## License

Apache-2.0
93 changes: 93 additions & 0 deletions src/ros2_medkit_fault_manager/design/architecture.puml
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
@startuml ros2_medkit_fault_manager_architecture

skinparam linetype ortho
skinparam classAttributeIconSize 0

title ROS 2 Medkit Fault Manager - Class Architecture

package "ROS 2 Framework" {
class "rclcpp::Node" {
+create_service()
+get_logger()
+now()
}
}

package "ros2_medkit_msgs" {
class "msg::Fault" {
+fault_code: string
+severity: uint8
+description: string
+first_occurred: Time
+last_occurred: Time
+occurrence_count: uint32
+status: string
+reporting_sources: string[]
}

class "srv::ReportFault" {
+Request: fault_code, severity, description, source_id
+Response: success, message
}

class "srv::GetFaults" {
+Request: filter_by_severity, severity, statuses
+Response: faults[]
}

class "srv::ClearFault" {
+Request: fault_code
+Response: success, message
}
}

package "ros2_medkit_fault_manager" {

class FaultManagerNode {
+ get_storage(): FaultStorage&
}

abstract class FaultStorage <<interface>> {
+ {abstract} report_fault(): bool
+ {abstract} get_faults(): vector<Fault>
+ {abstract} get_fault(): optional<Fault>
+ {abstract} clear_fault(): bool
+ {abstract} size(): size_t
+ {abstract} contains(): bool
}

class InMemoryFaultStorage {
+ report_fault(): bool
+ get_faults(): vector<Fault>
+ get_fault(): optional<Fault>
+ clear_fault(): bool
+ size(): size_t
+ contains(): bool
}

class FaultState <<struct>> {
+ to_msg(): Fault
}
}

' Relationships

' Inheritance
FaultManagerNode -up-|> "rclcpp::Node" : extends
InMemoryFaultStorage -up-|> FaultStorage : implements

' Composition
FaultManagerNode *-down-> InMemoryFaultStorage : owns

' InMemoryFaultStorage contains FaultStates
InMemoryFaultStorage o-right-> FaultState : contains many

' FaultState converts to message
FaultState ..> "msg::Fault" : converts to

' Node uses service types
FaultManagerNode ..> "srv::ReportFault" : handles
FaultManagerNode ..> "srv::GetFaults" : handles
FaultManagerNode ..> "srv::ClearFault" : handles

@enduml
Loading
Loading