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 +5/−0
- ogma-core.cabal +15/−11
- src/Command/ROSApp.hs +50/−5
- templates/ros/Dockerfile +9/−1
- templates/ros/screenrc +19/−0
- templates/ros/test_requirements/CMakeLists.txt +26/−0
- templates/ros/test_requirements/package.xml +20/−0
- templates/ros/test_requirements/src/test_requirements.cpp +192/−0
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;+}