packages feed

ogma-core 1.8.0 → 1.9.0

raw patch · 8 files changed

+336/−17 lines, 8 filesdep ~ogma-extradep ~ogma-language-cdep ~ogma-language-copilotPVP ok

version bump matches the API change (PVP)

Dependency ranges changed: ogma-extra, ogma-language-c, ogma-language-copilot, ogma-language-csv, ogma-language-jsonspec, ogma-language-lustre, ogma-language-smv, ogma-language-xlsx, ogma-language-xmlspec, ogma-spec

API changes (from Hackage documentation)

+ Command.ROSApp: Node :: String -> String -> Node
+ Command.ROSApp: [commandTestingApps] :: CommandOptions -> [Node]
+ Command.ROSApp: [commandTestingVars] :: CommandOptions -> [String]
+ Command.ROSApp: data Node
+ Command.ROSApp: instance Data.Aeson.Types.ToJSON.ToJSON Command.ROSApp.Node
+ Command.ROSApp: instance GHC.Generics.Generic Command.ROSApp.Node
- Command.ROSApp: CommandOptions :: Maybe String -> Maybe FilePath -> FilePath -> Maybe FilePath -> Maybe FilePath -> Maybe FilePath -> Maybe FilePath -> String -> String -> Maybe String -> Maybe FilePath -> CommandOptions
+ Command.ROSApp: CommandOptions :: Maybe String -> Maybe FilePath -> FilePath -> Maybe FilePath -> Maybe FilePath -> Maybe FilePath -> Maybe FilePath -> String -> String -> Maybe String -> Maybe FilePath -> [Node] -> [String] -> CommandOptions

Files

CHANGELOG.md view
@@ -1,5 +1,10 @@ # Revision history for ogma-core +## [1.9.0] - 2025-08-06++* Version bump 1.9.0 (#284).+* Extend ROS backend to generate testing package (#280).+ ## [1.8.0] - 2025-07-13  * Version bump 1.8.0 (#275).
ogma-core.cabal view
@@ -32,7 +32,7 @@ build-type:          Simple  name:                ogma-core-version:             1.8.0+version:             1.9.0 homepage:            https://github.com/nasa/ogma bug-reports:         https://github.com/nasa/ogma/issues license:             OtherLicense@@ -69,11 +69,15 @@                      templates/copilot-cfs/fsw/src/copilot_cfs.h                      templates/copilot-cfs/fsw/src/copilot_cfs_events.h                      templates/ros/Dockerfile+                     templates/ros/screenrc                      templates/ros/copilot/CMakeLists.txt                      templates/ros/copilot/src/copilot_logger.cpp                      templates/ros/copilot/src/copilot_monitor.cpp                      templates/ros/copilot/src/Copilot.hs                      templates/ros/copilot/package.xml+                     templates/ros/test_requirements/CMakeLists.txt+                     templates/ros/test_requirements/src/test_requirements.cpp+                     templates/ros/test_requirements/package.xml                      templates/diagram/Copilot.hs                      templates/fprime/CMakeLists.txt                      templates/fprime/Copilot.cpp@@ -148,16 +152,16 @@     , process                 >= 1.6      && < 1.7     , text                    >= 1.2.3.1  && < 2.2 -    , ogma-extra              >= 1.8.0 && < 1.9-    , ogma-language-c         >= 1.8.0 && < 1.9-    , ogma-language-copilot   >= 1.8.0 && < 1.9-    , ogma-language-csv       >= 1.8.0 && < 1.9-    , ogma-language-jsonspec  >= 1.8.0 && < 1.9-    , ogma-language-lustre    >= 1.8.0 && < 1.9-    , ogma-language-smv       >= 1.8.0 && < 1.9-    , ogma-language-xlsx      >= 1.8.0 && < 1.9-    , ogma-language-xmlspec   >= 1.8.0 && < 1.9-    , ogma-spec               >= 1.8.0 && < 1.9+    , ogma-extra              >= 1.9.0 && < 1.10+    , ogma-language-c         >= 1.9.0 && < 1.10+    , ogma-language-copilot   >= 1.9.0 && < 1.10+    , ogma-language-csv       >= 1.9.0 && < 1.10+    , ogma-language-jsonspec  >= 1.9.0 && < 1.10+    , ogma-language-lustre    >= 1.9.0 && < 1.10+    , ogma-language-smv       >= 1.9.0 && < 1.10+    , ogma-language-xlsx      >= 1.9.0 && < 1.10+    , ogma-language-xmlspec   >= 1.9.0 && < 1.10+    , ogma-spec               >= 1.9.0 && < 1.10    hs-source-dirs:     src
src/Command/ROSApp.hs view
@@ -44,6 +44,7 @@ module Command.ROSApp     ( command     , CommandOptions(..)+    , Node(Node)     , ErrorCode     )   where@@ -119,10 +120,18 @@                      (map (\x -> (x, Nothing)))                      rs -    let appData   = AppData variables monitors' copilotM+    let appData =+          AppData variables monitors' copilotM testingAdditionalApps testingVars+         variables = mapMaybe (variableMap varDB) varNames         monitors' = mapMaybe (monitorMap varDB) monitors +        testingVars+          | null testingLimitedVars+          = variables+          | otherwise+          = filter (\x -> varDeclName x `elem` testingLimitedVars) variables+     return appData    where@@ -145,6 +154,9 @@     processSpec spec' expr' fp' =       Command.Standalone.commandLogic expr' fp' "copilot" [] exprT spec' +    testingAdditionalApps = commandTestingApps options+    testingLimitedVars    = commandTestingVars options+ -- ** Argument processing  -- | Options used to customize the conversion of specifications to ROS@@ -174,6 +186,10 @@   , commandExtraVars   :: Maybe FilePath -- ^ File containing additional                                          -- variables to make available to the                                          -- template.+  , commandTestingApps :: [Node]         -- ^ Additional applications to turn+                                         -- on during testing.+  , commandTestingVars :: [String]       -- ^ Limited list of variables to use+                                         -- for testing.   }  -- | Return the variable information needed to generate declarations@@ -192,7 +208,7 @@   let typeMsg' = fromMaybe                    (topicType topicDef)                    (typeFromType <$> findType varDB varName "ros/message" "C")-  return $ VarDecl varName typeVar' mid typeMsg'+  return $ VarDecl varName typeVar' mid typeMsg' (randomBaseType typeVar')  -- | Return the monitor information needed to generate declarations and -- publishers for the given monitor info, and variable database.@@ -212,6 +228,7 @@     , varDeclType    :: String     , varDeclId      :: String     , varDeclMsgType :: String+    , varDeclRandom  :: String     }   deriving Generic @@ -228,12 +245,40 @@  instance ToJSON Monitor +-- | A package-qualified ROS 2 node name.+data Node = Node+    { nodePackage :: String+    , nodeName    :: String+    }+  deriving Generic++instance ToJSON Node+ -- | Data that may be relevant to generate a ROS application. data AppData = AppData-  { variables :: [VarDecl]-  , monitors  :: [Monitor]-  , copilot   :: Maybe Command.Standalone.AppData+  { variables        :: [VarDecl]+  , monitors         :: [Monitor]+  , copilot          :: Maybe Command.Standalone.AppData+  , testingApps      :: [Node]+  , testingVariables :: [VarDecl]   }   deriving (Generic)  instance ToJSON AppData++-- | Name of the function to be used to generate random values of a given type.+randomBaseType :: String -- ^ Type to generate random values of.+               -> String+randomBaseType ty = case ty of+  "bool"     -> "randomBool"+  "uint8_t"  -> "randomInt"+  "uint16_t" -> "randomInt"+  "uint32_t" -> "randomInt"+  "uint64_t" -> "randomInt"+  "int8_t"   -> "randomInt"+  "int16_t"  -> "randomInt"+  "int32_t"  -> "randomInt"+  "int64_t"  -> "randomInt"+  "float"    -> "randomFloat"+  "double"   -> "randomFloat"+  def        -> def
templates/ros/Dockerfile view
@@ -6,6 +6,7 @@  RUN mkdir -p ${PACKAGE_PATH}/src/ ADD copilot ${PACKAGE_PATH}/src/copilot+ADD test_requirements ${PACKAGE_PATH}/src/test_requirements USER root RUN chown -R ${USER} ${PACKAGE_PATH} USER ${USER}@@ -13,4 +14,11 @@ SHELL ["/bin/bash", "-c"] WORKDIR ${PACKAGE_PATH} RUN source /opt/spaceros/install/setup.bash && \-    colcon build+    colcon build --packages-select copilot           --install-base /opt/spaceros/install/ && \+    colcon build --packages-select test_requirements --install-base /opt/spaceros/install/++ADD screenrc /home/spaceros-user/.screenrc+USER root+ADD screenrc /root/.screenrc+RUN chown -R spaceros-user /home/spaceros-user/.screenrc+USER spaceros-user
+ templates/ros/screenrc view
@@ -0,0 +1,19 @@+deflogin off+startup_message off++chdir /demo+screen -t "Copilot RV" bash -c 'bash --init-file <(echo "source /home/spaceros-user/spaceros/install/setup.bash; set -x; ros2 run copilot copilot")'++{{#testingApps}}+split+focus+screen -t "{{nodeName}}" bash -c 'bash --init-file <(echo "source /home/spaceros-user/spaceros/install/setup.bash; set -x; ros2 run {{nodePackage}} {{nodeName}}")'++{{/testingApps}}+split+focus+screen -t "Requirements testing" bash -c 'sleep 2; bash --init-file <( echo "source /home/spaceros-user/spaceros/install/setup.bash; set -x; ros2 run test_requirements test_requirements")'++split+focus+screen -t "<interactive shell>" bash -c 'bash --init-file <(echo "echo This is an interactive shell. Send data to topics with ros2 topic pub \<type\> \<data\>; source /home/spaceros-user/spaceros/install/setup.bash")'
+ templates/ros/test_requirements/CMakeLists.txt view
@@ -0,0 +1,26 @@+cmake_minimum_required(VERSION 3.8)+project(test_requirements)++if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")+  add_compile_options(-Wall -Wextra -Wpedantic)+endif()++# find dependencies+find_package(ament_cmake REQUIRED)+find_package(rclcpp REQUIRED)+find_package(std_msgs REQUIRED)++# Uncomment to enable compiling the requirement tests+add_executable(test_requirements src/test_requirements.cpp)+ament_target_dependencies(test_requirements rclcpp std_msgs)++install(TARGETS+  test_requirements+  DESTINATION lib/${PROJECT_NAME})++if(BUILD_TESTING)+  find_package(ament_lint_auto REQUIRED)+  ament_lint_auto_find_test_dependencies()+endif()++ament_package()
+ templates/ros/test_requirements/package.xml view
@@ -0,0 +1,20 @@+<?xml version="1.0"?>+<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>+<package format="3">+  <name>test_requirements</name>+  <version>0.0.0</version>+  <description>TODO: Package description</description>+  <maintainer email="root@todo.todo">root</maintainer>+  <license>TODO: License declaration</license>++  <buildtool_depend>ament_cmake</buildtool_depend>+  <depend>rclcpp</depend>+  <depend>std_msgs</depend>++  <test_depend>ament_lint_auto</test_depend>+  <test_depend>ament_lint_common</test_depend>++  <export>+    <build_type>ament_cmake</build_type>+  </export>+</package>
+ templates/ros/test_requirements/src/test_requirements.cpp view
@@ -0,0 +1,192 @@+#include <functional>+#include <memory>++#include "gtest/gtest.h"++#include "rclcpp/rclcpp.hpp"++#include "std_msgs/msg/bool.hpp"+#include "std_msgs/msg/empty.hpp"+#include "std_msgs/msg/u_int8.hpp"+#include "std_msgs/msg/u_int16.hpp"+#include "std_msgs/msg/u_int32.hpp"+#include "std_msgs/msg/u_int64.hpp"+#include "std_msgs/msg/int8.hpp"+#include "std_msgs/msg/int16.hpp"+#include "std_msgs/msg/int32.hpp"+#include "std_msgs/msg/int64.hpp"+#include "std_msgs/msg/float32.hpp"+#include "std_msgs/msg/float64.hpp"+#include <cstdint>++using std::placeholders::_1;++class RequirementsTest : public rclcpp::Node {+  public:+    RequirementsTest() : Node("requirementstest") {++      declare_parameter("testing_seed", 0); // defaults to 0+      declare_parameter("testing_deadline", 2); // defaults to 2 secs+++{{#testingVariables}}+      {{varDeclName}}_publisher_ = this->create_publisher<{{varDeclMsgType}}>(+        "{{varDeclId}}", 10);++{{/testingVariables}}++{{#monitors}}+      {{monitorName}}_subscription_ = this->create_subscription<std_msgs::msg::Empty>(+        "copilot/{{monitorName}}", 10,+        std::bind(&RequirementsTest::{{monitorName}}_callback, this, _1));++{{/monitors}}++      get_parameter("testing_seed", initial_seed);+      get_parameter("testing_deadline", deadline);++      std::srand((unsigned int)this->initial_seed);++      this->seed = this->initial_seed;+      this->max_tests = calculate_num_tests();++      rclcpp::Duration update_period = rclcpp::Duration::from_seconds(1);+      timerInit = rclcpp::create_timer(this->get_node_base_interface(),+                                       this->get_node_timers_interface(),+                                       this->get_node_clock_interface()->get_clock(),+                                       update_period,+                                       std::bind(&RequirementsTest::tests_init, this)+                                       );+    }++  private:+++{{#monitors}}+    bool violation_{{monitorName}} = false;++{{/monitors}}+    bool violations = false;+++{{#testingVariables}}+    rclcpp::Publisher<{{varDeclMsgType}}>::SharedPtr {{varDeclName}}_publisher_;++{{/testingVariables}}+{{#monitors}}+    void {{monitorName}}_callback(const std_msgs::msg::Empty::SharedPtr msg) {+        this->violation_{{monitorName}} = true;+        this->violations = true;+    }+{{/monitors}}++{{#monitors}}+    rclcpp::Subscription<std_msgs::msg::Empty>::SharedPtr {{monitorName}}_subscription_;++{{/monitors}}+++    int initial_seed; // To be configured using a parameter.+    int seed;         // To be configured using a parameter.+    int deadline;     // To be configured using a parameter.++    int max_tests;+    int num_test = 0;++    // Calculate the number of tests to be executed+    int calculate_num_tests() {+       return abs(std::rand());+    }++    rclcpp::TimerBase::SharedPtr timerResult;+    rclcpp::TimerBase::SharedPtr timerInit;++    void tests_init () {+        timerInit->cancel();+        tests_step_send();+    }++    void tests_step_send () {++{{#testingVariables}}+       {{varDeclType}} {{varDeclName}}_data = {{varDeclRandom}}();+       auto {{varDeclName}}_data_msg = {{varDeclMsgType}}();+       {{varDeclName}}_data_msg.data = {{varDeclName}}_data;+       {{varDeclName}}_publisher_->publish({{varDeclName}}_data_msg);++{{/testingVariables}}+++        rclcpp::Duration update_period = rclcpp::Duration::from_seconds(deadline);+        timerResult = rclcpp::create_timer(this->get_node_base_interface(),+                                           this->get_node_timers_interface(),+                                           this->get_node_clock_interface()->get_clock(),+                                           update_period,+                                           std::bind(&RequirementsTest::tests_step_result, this)+                                           );+    }++    void tests_step_result () {+        timerResult->cancel();++{{#monitors}}+        if (this->violation_{{monitorName}}) {+            this->publish_violation("{{monitorName}}");+        }++{{/monitors}}++       this->num_test++;++       // Stop if out of steps or there have been violations+       if ((this->num_test >= this->max_tests) || violations) {+         // Terminate using the gtest mechanism to indicate the result+         if (violations) {+           RCLCPP_INFO(this->get_logger(), "Tests failed");+           // FAIL();+         } else {+           RCLCPP_INFO(this->get_logger(), "Tests succeeded");+           // SUCCEED();+         }+         rclcpp::shutdown();+       } else {+         tests_step_send();+       }+    }++    float randomFloat() {+       int numerator = rand();+       int denominator = rand();++       // Ensure that we do not divide by zero.+       if (denominator == 0) {+           denominator = 1;+       }++       return (float)numerator / (float)denominator;+    }++    int randomInt() {+       return rand();+    }++    bool randomBool() {+       return rand() & 1;+    }++    void delay(int time) {+       rclcpp::sleep_for(std::chrono::seconds(time));+    }++    void publish_violation (const char* requirement) {+        RCLCPP_INFO(this->get_logger(), "Requirement violation. Req: %s; Seed: %d; Step: %d\\n",+            requirement, this->initial_seed, this->num_test);+    }+};++int main(int argc, char* argv[]) {+  rclcpp::init(argc, argv);+  rclcpp::spin(std::make_shared<RequirementsTest>());+  rclcpp::shutdown();+  return 0;+}