From deb6ccff661a0fe54a46557ee0761cf781989758 Mon Sep 17 00:00:00 2001 From: Cat Date: Thu, 7 Nov 2024 14:17:55 -0600 Subject: [PATCH 01/18] Created GeneratorPy --- .../hamr/codegen/ros2/GeneratorPy.scala | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/shared/src/main/scala/org/sireum/hamr/codegen/ros2/GeneratorPy.scala b/shared/src/main/scala/org/sireum/hamr/codegen/ros2/GeneratorPy.scala index 2a31a2c1..d5279472 100644 --- a/shared/src/main/scala/org/sireum/hamr/codegen/ros2/GeneratorPy.scala +++ b/shared/src/main/scala/org/sireum/hamr/codegen/ros2/GeneratorPy.scala @@ -24,25 +24,25 @@ object GeneratorPy { def genPyLaunchFileName(compNameS: String): String = { // create launch file name - val nodeNameT: String = s"$compNameS$py_launch_file_name_suffix" + val nodeNameT: String = s"${compNameS}${py_launch_file_name_suffix}" return nodeNameT } def genPyPackageName(packageNameS: String): String = { // create target package name - val packageNameT: String = s"$packageNameS$py_package_name_suffix" + val packageNameT: String = s"${packageNameS}${py_package_name_suffix}" return packageNameT } def genPyNodeSourceName(compNameS: String): String = { // create target node name - val nodeNameT: String = s"$compNameS$py_src_node_name_suffix" + val nodeNameT: String = s"${compNameS}${py_src_node_name_suffix}" return nodeNameT } def genExecutableFileName(componentNameS: String): String = { // create target executable name - val executableFileNameT: String = s"$componentNameS$node_executable_filename_suffix" + val executableFileNameT: String = s"${componentNameS}${node_executable_filename_suffix}" return executableFileNameT } @@ -82,8 +82,8 @@ object GeneratorPy { val node_source_file_nameT = genPyNodeSourceName(componentName) val py_package_nameT = genPyPackageName(modelName) val node_executable_file_nameT = genExecutableFileName(componentName) - val entryPointDecl: ST - = st"\"$node_executable_file_nameT = $py_package_nameT.$node_source_file_nameT:$py_src_node_entry_point_name\"" + val entryPointDecl:ST + = st"\"${node_executable_file_nameT} = ${py_package_nameT}.${node_source_file_nameT}:${py_src_node_entry_point_name}\"" return entryPointDecl } @@ -102,11 +102,11 @@ object GeneratorPy { } val setupFileBody = - st"""# $fileName in src/$top_level_package_nameT + st"""# ${fileName} in src/${top_level_package_nameT} | |from setuptools import find_packages, setup | - |package_name = '$top_level_package_nameT' + |package_name = '${top_level_package_nameT}' | |setup( | name=package_name, @@ -141,7 +141,7 @@ object GeneratorPy { var requirements: ISZ[ST] = IS() for (pkg <- packages) { - requirements = requirements :+ st"$pkg" + requirements = requirements :+ st"${pkg}" } return requirements @@ -201,7 +201,7 @@ object GeneratorPy { | | ament_cmake | - | $top_level_package_nameT + | ${top_level_package_nameT} | | ament_lint_auto | ament_lint_common @@ -337,7 +337,7 @@ object GeneratorPy { // Int32 is a placeholder message value val subscriptionMessageHeader: ST = st"""self.get_${portName}() - | MsgType msg = applicationIn_$portName.front() + | MsgType msg = applicationIn_${portName}.front() | return (Int32)msg |""" return subscriptionMessageHeader From 76e4db36aa09e13b5c3b25e6d0ec46b7360cae43 Mon Sep 17 00:00:00 2001 From: Cat Date: Mon, 16 Dec 2024 11:57:07 -0600 Subject: [PATCH 02/18] Fixing syntax --- .../main/scala/org/sireum/hamr/codegen/ros2/GeneratorPy.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/src/main/scala/org/sireum/hamr/codegen/ros2/GeneratorPy.scala b/shared/src/main/scala/org/sireum/hamr/codegen/ros2/GeneratorPy.scala index d5279472..642e6359 100644 --- a/shared/src/main/scala/org/sireum/hamr/codegen/ros2/GeneratorPy.scala +++ b/shared/src/main/scala/org/sireum/hamr/codegen/ros2/GeneratorPy.scala @@ -82,7 +82,7 @@ object GeneratorPy { val node_source_file_nameT = genPyNodeSourceName(componentName) val py_package_nameT = genPyPackageName(modelName) val node_executable_file_nameT = genExecutableFileName(componentName) - val entryPointDecl:ST + val entryPointDecl: ST = st"\"${node_executable_file_nameT} = ${py_package_nameT}.${node_source_file_nameT}:${py_src_node_entry_point_name}\"" return entryPointDecl } From 43321f43d5c33f4ad95dfe289b8f312a481acd3b Mon Sep 17 00:00:00 2001 From: Cat Date: Fri, 7 Feb 2025 12:16:05 -0600 Subject: [PATCH 03/18] Refactoring Datatypes in GeneratorPy --- .../hamr/codegen/ros2/GeneratorPy.scala | 229 ++++++++++++------ 1 file changed, 150 insertions(+), 79 deletions(-) diff --git a/shared/src/main/scala/org/sireum/hamr/codegen/ros2/GeneratorPy.scala b/shared/src/main/scala/org/sireum/hamr/codegen/ros2/GeneratorPy.scala index 642e6359..a52d0717 100644 --- a/shared/src/main/scala/org/sireum/hamr/codegen/ros2/GeneratorPy.scala +++ b/shared/src/main/scala/org/sireum/hamr/codegen/ros2/GeneratorPy.scala @@ -3,10 +3,16 @@ package org.sireum.hamr.codegen.ros2 import org.sireum._ -import org.sireum.hamr.codegen.common.symbols.{AadlDataPort, AadlPort, AadlThread, Dispatch_Protocol} +import org.sireum.hamr.codegen.common.symbols.{AadlDataPort, AadlEventDataPort, AadlPort, AadlThread, Dispatch_Protocol} +import org.sireum.hamr.codegen.common.types.AadlType import org.sireum.hamr.ir.Direction +import org.sireum.message.Reporter +import org.sireum.ops.ISZOps object GeneratorPy { + + val toolName: String = "Ros2Codegen" + val node_executable_filename_suffix: String = "_exe" val launch_node_decl_suffix: String = "_node" val py_launch_file_name_suffix: String = ".launch.py" @@ -58,6 +64,31 @@ object GeneratorPy { return names } + def genPortDatatype(port: AadlPort, packageName: String, datatypeMap: Map[AadlType, (String, ISZ[String])], reporter: Reporter): String = { + val s: String = port match { + case dp: AadlDataPort => + val dtype = datatypeMap.get(dp.aadlType) + if (dtype.nonEmpty) { + s"${dtype.get._1}" + } + else { + reporter.error(None(), toolName, s"Port ${port.identifier}: datatype unknown, setting datatype to Empty") + s"Empty" + } + case edp: AadlEventDataPort => + val dtype = datatypeMap.get(edp.aadlType) + if (dtype.nonEmpty) { + s"${dtype.get._1}" + } + else { + reporter.error(None(), toolName, s"Port ${port.identifier}: datatype unknown, setting datatype to Empty") + s"Empty" + } + case _ => s"Empty" + } + return s + } + def seqToString(seq: ISZ[String], separator: String): String = { var str = "" for (s <- seq) { @@ -304,55 +335,61 @@ object GeneratorPy { // 'fanCmd', // self.handle_fanCmd, // 10) - def genPyTopicSubscrptionStrict(inPort: AadlPort, nodeName: String, isSporadic: B): ST = { + def genPyTopicSubscriptionStrict(inPort: AadlPort, isSporadic: B, portType: String): ST = { val topicName = seqToString(inPort.path, "_") val portName = inPort.identifier val handler: ST = if (!isSporadic || inPort.isInstanceOf[AadlDataPort]) { - st"enqueue(infrastructureIn_${portName}, msg)" + st"self.enqueue(infrastructureIn_${portName}, msg)" } else { // TODO: CPP to Py - st"""enqueue(infrastructureIn_${portName}, msg) - | - |""" + st"""self.enqueue(infrastructureIn_${portName}, msg); + |std::thread([this]() { + | threading.Lock() + | self.receiveInputs(infrastructureIn_${portName}, applicationIn_${portName}); + | if (applicationIn_${portName}.empty()) return; + | self.handle_${portName}_base(applicationIn_${portName}.front()); + | applicationIn_${portName}.pop(); + | self.sendOutputs(); + |}).detach();""" } // Int32 is a placeholder message value val portCode: ST = st"""self.${topicName}_subscription_ = self.create_subscription( - | Int32, + | ${portType}, | "${topicName}", | self.${handler}, | 1, - | ${subscription_options_name}) + | callback_group=self.${subscription_options_name}) |""" return portCode } - def genPyGetApplicationInValue(inPort: AadlPort, nodeName: String): ST = { + def genPyGetApplicationInValue(inPort: AadlPort, portType: String): ST = { val portName = inPort.identifier // Int32 is a placeholder message value val subscriptionMessageHeader: ST = st"""self.get_${portName}() | MsgType msg = applicationIn_${portName}.front() - | return (Int32)msg + | return get<${portType}>(msg) |""" return subscriptionMessageHeader } - def genPySubscriptionHandlerBaseSporadic(inPort: AadlPort, nodeName: String): ST = { + def genPySubscriptionHandlerBaseSporadic(inPort: AadlPort, portType: String): ST = { val handlerName = inPort.identifier // Int32 is a placeholder message value val handlerCode: ST = st"""def handle_${handlerName}_base(self, msg): - | if isinstance(&msg, Int32): + | if isinstance(get_if<${portType}>(&msg), Int32): | handle_${handlerName}(*typedMsg) | else: - | PRINT_ERROR("Sending out wrong type of variable on port ${handlerName}.\nThis shouldn't be possible. If you are seeing this message, please notify this tool's current maintainer.") + | this.get_logger().error("Sending out wrong type of variable on port ${handlerName}.\nThis shouldn't be possible. If you are seeing this message, please notify this tool's current maintainer.") |""" return handlerCode } @@ -363,7 +400,7 @@ object GeneratorPy { // "${inPortName}", // 10, // callback_group=self.${callback_group_name}) - def genPyTopicPublisher(outPort: AadlPort, inPortNames: ISZ[String]): ST = { + def genPyTopicPublisher(outPort: AadlPort, portType: String, inPortNames: ISZ[String]): ST = { val portName = seqToString(outPort.path, "_") if (inPortNames.size == 1) { @@ -372,7 +409,7 @@ object GeneratorPy { // Int32 is a placeholder message value val portCode: ST = st"""self.${portName}_publisher_ = self.create_publisher( - | Int32, + | ${portType}, | "${inPortName}", | 1) |""" @@ -386,7 +423,7 @@ object GeneratorPy { for (inPortName <- inPortNames) { outputInstances = outputInstances :+ st"""self.${portName}_publisher_${counter} = self.create_publisher( - | Int32, + | ${portType} | "${inPortName}", | 1) |""" @@ -399,7 +436,7 @@ object GeneratorPy { return fanPortCode } - def genPyTopicPublishMethodStrict(outPort: AadlPort, nodeName: String, inputPortCount: Z): ST = { + def genPyTopicPublishMethodStrict(outPort: AadlPort, portType: String, inputPortCount: Z): ST = { val portName = seqToString(outPort.path, "_") val handlerName = outPort.identifier @@ -418,21 +455,21 @@ object GeneratorPy { // Int32 is a placeholder message value val publisherCode: ST = st"""def sendOut_${handlerName}(self, msg): - | if isinstance(&msg, Int32): + | if isinstance(get_if<${portType}>(&msg), Int32): | ${(publishers, "\n")} | else: - | PRINT_ERROR("Sending out wrong type of variable on port ${handlerName}.\nThis shouldn't be possible. If you are seeing this message, please notify this tool's current maintainer.") + | this.get_logger().error("Sending out wrong type of variable on port ${handlerName}.\nThis shouldn't be possible. If you are seeing this message, please notify this tool's current maintainer.") |""" return publisherCode } - def genPyPutMsgMethodStrict(outPort: AadlPort, nodeName: String): ST = { + def genPyPutMsgMethodStrict(outPort: AadlPort): ST = { val handlerName = outPort.identifier // Int32 is a placeholder message value val putMsgCode: ST = st"""def put_${handlerName}(self, msg): - | enqueue(applicationOut_${handlerName}, msg) + | self.enqueue(applicationOut_${handlerName}, msg) |""" return putMsgCode } @@ -443,28 +480,31 @@ object GeneratorPy { // 'fanCmd', // self.handle_fanCmd, // 10) - def genPyTopicSubscription(inPort: AadlPort, nodeName: String): ST = { + def genPyTopicSubscription(inPort: AadlPort, portType: String): ST = { val topicName = seqToString(inPort.path, "_") val portName = inPort.identifier // Int32 in a placeholder message value val portCode: ST = st"""self.${topicName}_subscription_ = self.create_subscription( - | Int32, + | ${portType}, | "${topicName}", + | self.handle_${portName}, | 1, - | ${subscription_options_name} + | callback_group=self.${subscription_options_name} |""" return portCode } - def genPySubscriptionHandlerPeriodic(inPort: AadlPort, nodeName: String): ST = { + def genPySubscriptionHandlerPeriodic(inPort: AadlPort, portType: String): ST = { val handlerName = inPort.identifier // Int32 is a placeholder message value val subscriptionHandlerHeader: ST = st"""def handle_${handlerName}(self, msg): - | self.${handlerName}_msg_holder = msg + | msgNew = ${portType}() + | msgNew = msg.data + | self.${handlerName}_msg_holder = msgNew |""" return subscriptionHandlerHeader } @@ -475,11 +515,21 @@ object GeneratorPy { // Int32 is a placeholder message value val subscriptionMessage: ST = st"""def get_${portName}(): - | return ${portName}_msg_holder + | return self.${portName}_msg_holder |""" return subscriptionMessage } + def genPyFileMsgTypeIncludes(packageName: String, msgTypes: ISZ[String]): ISZ[ST] = { + var includes: ISZ[ST] = IS() + + for (msgType <- msgTypes) { + includes = includes :+ st"from ${packageName}_interfaces.msg import ${msgType}" + } + + return includes + } + def genPyTopicPublishMethod(outPort: AadlPort, nodeName: String, inputPortCount: Z): ST = { val portName = seqToString(outPort.path, "_") val handlerName = outPort.identifier @@ -514,7 +564,7 @@ object GeneratorPy { val period = component.period.get val timer: ST = - st"""self.periodTimer_ = self.create_wall_timer(${period}, self.timeTriggeredCaller, ${callback_group_name})""" + st"""self.periodTimer_ = self.create_wall_timer(${period}, self.timeTriggeredCaller, callback_group=self.${callback_group_name})""" return timer } @@ -522,7 +572,7 @@ object GeneratorPy { val period = component.period.get val timer: ST = - st"""self.periodTimer_ = self.create_wall_timer(${period}, self.timeTriggered, ${callback_group_name})""" + st"""self.periodTimer_ = self.create_wall_timer(${period}, self.timeTriggered, callback_group=self.${callback_group_name})""" return timer } @@ -571,9 +621,9 @@ object GeneratorPy { def genPyTimeTriggeredCaller(nodeName: String): ST = { val timeTriggered: ST = st"""def timeTriggeredCaller(self): - | receiveInputs() + | self.receiveInputs() | timeTriggered() - | sendOutputs() + | self.sendOutputs() """ return timeTriggered } @@ -584,13 +634,13 @@ object GeneratorPy { | if !infrastructureQueue.empty(): | eventMsg = infrastructureQueue.front() | infrastructureQueue.pop() - | enqueue(applicationQueue, eventMsg) + | self.enqueue(applicationQueue, eventMsg) | | for port in inDataPortTupleVector: | infrastructureQueue = std::get<0>(port) | if !infrastructureQueue.empty(): | msg = infrastructureQueue.front() - | enqueue(*std::get<1>(port), msg) + | self.enqueue(*std::get<1>(port), msg) """ return method } @@ -602,14 +652,14 @@ object GeneratorPy { | auto infrastructureQueue = std::get<0>(port) | if !infrastructureQueue.empty(): | msg = infrastructureQueue.front() - | enqueue(*std::get<1>(port), msg) + | self.enqueue(*std::get<1>(port), msg) | | for port in inEventPortTupleVector: | auto infrastructureQueue = std::get<0>(port) | if !infrastructureQueue.empty(): | msg = infrastructureQueue.front() | infrastructureQueue->pop() - | enqueue(*std::get<1>(port), msg) + | self.enqueue(*std::get<1>(port), msg) """ return method } @@ -645,7 +695,7 @@ object GeneratorPy { } def genPyBaseNodePyFile(packageName: String, component: AadlThread, connectionMap: Map[ISZ[String], ISZ[ISZ[String]]], - strictAADLMode: B): (ISZ[String], ST) = { + datatypeMap: Map[AadlType, (String, ISZ[String])], strictAADLMode: B, reporter: Reporter): (ISZ[String], ST) = { val nodeName = s"${component.pathAsString("_")}_base" val fileName = genPyNodeSourceName(nodeName) @@ -659,19 +709,24 @@ object GeneratorPy { var inTuplePortNames: ISZ[String] = IS() var strictPutMsgMethods: ISZ[ST] = IS() var strictSubscriptionHandlerBaseMethods: ISZ[ST] = IS() + var msgTypes: ISZ[String] = IS() var hasInPorts = F for (p <- component.getPorts()) { + val portDatatype: String = genPortDatatype(p, packageName, datatypeMap, reporter) + if (!ISZOps(msgTypes).contains(portDatatype)) { + msgTypes = msgTypes :+ portDatatype + } if (strictAADLMode) { if (p.direction == Direction.In) { - subscribers = subscribers :+ genPyTopicSubscrptionStrict(p, nodeName, isSporadic(component)) + subscribers = subscribers :+ genPyTopicSubscriptionStrict(p, isSporadic(component), portDatatype) if (!isSporadic(component) || p.isInstanceOf[AadlDataPort]) { inTuplePortNames = inTuplePortNames :+ p.identifier - subscriptionMessageGetters = subscriptionMessageGetters :+ genPyGetApplicationInValue(p, nodeName) + subscriptionMessageGetters = subscriptionMessageGetters :+ genPyGetApplicationInValue(p, portDatatype) } else { strictSubscriptionHandlerBaseMethods = strictSubscriptionHandlerBaseMethods :+ - genPySubscriptionHandlerBaseSporadic(p, nodeName) + genPySubscriptionHandlerBaseSporadic(p, portDatatype) } hasInPorts = T } @@ -680,23 +735,23 @@ object GeneratorPy { if (connectionMap.get(p.path).nonEmpty) { val inputPorts = connectionMap.get(p.path).get val inputPortNames = getPortNames(inputPorts) - publishers = publishers :+ genPyTopicPublisher(p, inputPortNames) + publishers = publishers :+ genPyTopicPublisher(p, portDatatype, inputPortNames) publisherMethods = publisherMethods :+ - genPyTopicPublishMethodStrict(p, nodeName, inputPortNames.size) + genPyTopicPublishMethodStrict(p, portDatatype, inputPortNames.size) } else { publisherMethods = publisherMethods :+ - genPyTopicPublishMethodStrict(p, nodeName, 0) + genPyTopicPublishMethodStrict(p, portDatatype, 0) } - strictPutMsgMethods = strictPutMsgMethods :+ genPyPutMsgMethodStrict(p, nodeName) + strictPutMsgMethods = strictPutMsgMethods :+ genPyPutMsgMethodStrict(p) } } else { if (p.direction == Direction.In) { - subscribers = subscribers :+ genPyTopicSubscription(p, nodeName) + subscribers = subscribers :+ genPyTopicSubscription(p, portDatatype) if (!isSporadic(component) || p.isInstanceOf[AadlDataPort]) { subscriberMethods = subscriberMethods :+ - genPySubscriptionHandlerPeriodic(p, nodeName) + genPySubscriptionHandlerPeriodic(p, portDatatype) subscriptionMessageGetters = subscriptionMessageGetters :+ genPyGetSubscriptionMessage(p, nodeName) } hasInPorts = T @@ -705,7 +760,7 @@ object GeneratorPy { if (connectionMap.get(p.path).nonEmpty) { val inputPorts = connectionMap.get(p.path).get val inputPortNames = getPortNames(inputPorts) - publishers = publishers :+ genPyTopicPublisher(p, inputPortNames) + publishers = publishers :+ genPyTopicPublisher(p, portDatatype, inputPortNames) publisherMethods = publisherMethods :+ genPyTopicPublishMethod(p, nodeName, inputPortNames.size) } @@ -713,10 +768,24 @@ object GeneratorPy { } } + val typeIncludes: ISZ[ST] = genPyFileMsgTypeIncludes(packageName, msgTypes) + var stdIncludes: ST = + st"""from queue import Queue""" + + if (strictAADLMode) { + stdIncludes = + st"""${stdIncludes} + |#include + |#include + |import threading""" + } + var fileBody = st"""#!/usr/bin/env python3 |import rclpy |from rclpy.node import Node + |${(stdIncludes, "\n")} + |${(typeIncludes, "\n")} | |//================================================= |// D O N O T E D I T T H I S F I L E @@ -828,12 +897,12 @@ object GeneratorPy { |${genPySendOutputs(nodeName)}""" } - val filePath: ISZ[String] = IS("src", packageName, "src", "base_code", fileName) + val filePath: ISZ[String] = IS("src", packageName, packageName, "base_code", fileName) return (filePath, fileBody) } - def genPySubscriptionHandlerSporadicStrict(inPort: AadlPort, nodeName: String): ST = { + def genPySubscriptionHandlerSporadicStrict(inPort: AadlPort): ST = { val handlerName = inPort.identifier // Int32 is a placeholder message value @@ -846,7 +915,7 @@ object GeneratorPy { return subscriptionHandlerHeader } - def genPySubscriptionHandlerSporadic(inPort: AadlPort, nodeName: String): ST = { + def genPySubscriptionHandlerSporadic(inPort: AadlPort): ST = { val handlerName = inPort.identifier // Int32 is a placeholder message value @@ -859,7 +928,7 @@ object GeneratorPy { return subscriptionHandlerHeader } - def genPyTimeTriggeredMethod(nodeName: String): ST = { + def genPyTimeTriggeredMethod(): ST = { val timeTriggered: ST = st"""def timeTriggered(self) |{ @@ -880,17 +949,17 @@ object GeneratorPy { if (p.direction == Direction.In && !p.isInstanceOf[AadlDataPort]) { if (strictAADLMode) { subscriptionHandlers = subscriptionHandlers :+ - genPySubscriptionHandlerSporadicStrict(p, nodeName) + genPySubscriptionHandlerSporadicStrict(p) } else { subscriptionHandlers = subscriptionHandlers :+ - genPySubscriptionHandlerSporadic(p, nodeName) + genPySubscriptionHandlerSporadic(p) } } } } else { - subscriptionHandlers = subscriptionHandlers :+ genPyTimeTriggeredMethod(nodeName) + subscriptionHandlers = subscriptionHandlers :+ genPyTimeTriggeredMethod() } val fileBody = @@ -902,7 +971,7 @@ object GeneratorPy { |//================================================= |def initialize(self) |{ - | PRINT_INFO("Initialize Entry Point invoked"); + | self.get_logger().info("Initialize Entry Point invoked"); | | // Initialize the node |} @@ -913,7 +982,7 @@ object GeneratorPy { |${(subscriptionHandlers, "\n")} """ - val filePath: ISZ[String] = IS("src", packageName, "src", "user_code", fileName) + val filePath: ISZ[String] = IS("src", packageName, packageName, "user_code", fileName) return (filePath, fileBody) } @@ -932,42 +1001,44 @@ object GeneratorPy { st"""#!/usr/bin/env python3 |import rclpy |from rclpy.node import Node - |//================================================= - |// D O N O T E D I T T H I S F I L E - |//================================================= + |from ${packageName}.user_code.${nodeName}_src import ${nodeName} + |#================================================= + |# D O N O T E D I T T H I S F I L E + |#================================================= | - |${nodeName}::${nodeName}() : ${nodeName}_base() - |{ - | // Invoke initialize entry point - | initialize(); + |class ${nodeName}(${nodeName}_base): + | def __init__(self): + | # invoke initialize entry point + | super().__init__() | - | PRINT_INFO("${nodeName} infrastructure set up"); - |} + | ${nodeName}() | - |int main(int argc, char **argv) - |{ - | rclcpp::init(argc, argv); - | auto executor = rclcpp::executors::MultiThreadedExecutor(); - | auto node = std::make_shared<${nodeName}>(); - | executor.add_node(node); - | executor.spin(); - | rclcpp::shutdown(); - | return 0; - |} + | self.get_logger().info("${nodeName} infrastructure set up") + | + |def main(args=None): + | rclpy.init(args=args) + | node = ${nodeName}() + | executor = MultiThreadedExecutor() + | executor.add_node(node) + | executor.spin() + | rclpy.shutdown() + | + |if __name__ == "__main__": + | main() """ - val filePath: ISZ[String] = IS("src", packageName, "src", "base_code", fileName) + val filePath: ISZ[String] = IS("src", packageName, packageName, "base_code", fileName) return (filePath, fileBody) } def genPyNodeFiles(modelName: String, threadComponents: ISZ[AadlThread], connectionMap: Map[ISZ[String], ISZ[ISZ[String]]], - strictAADLMode: B): ISZ[(ISZ[String], ST)] = { + datatypeMap: Map[AadlType, (String, ISZ[String])], strictAADLMode: B, reporter: Reporter): ISZ[(ISZ[String], ST)] = { val top_level_package_nameT: String = genPyPackageName(modelName) var py_files: ISZ[(ISZ[String], ST)] = IS() for (comp <- threadComponents) { py_files = - py_files :+ genPyBaseNodePyFile(top_level_package_nameT, comp, connectionMap, strictAADLMode) + py_files :+ genPyBaseNodePyFile(top_level_package_nameT, comp, connectionMap, datatypeMap, strictAADLMode, reporter) py_files = py_files :+ genPyUserNodePyFile(top_level_package_nameT, comp, strictAADLMode) py_files :+ genPyNodeRunnerFile(top_level_package_nameT, comp) @@ -981,13 +1052,13 @@ object GeneratorPy { // TODO: Python pkgs def genPyNodePkg(modelName: String, threadComponents: ISZ[AadlThread], connectionMap: Map[ISZ[String], ISZ[ISZ[String]]], - strictAADLMode: B): ISZ[(ISZ[String], ST)] = { + datatypeMap: Map[AadlType, (String, ISZ[String])], strictAADLMode: B, reporter: Reporter): ISZ[(ISZ[String], ST)] = { var files: ISZ[(ISZ[String], ST)] = IS() files = files :+ genPyFormatLaunchFile(modelName, threadComponents) files = files :+ genPySetupFile(modelName, threadComponents) - for(file <- genPyNodeFiles(modelName, threadComponents, connectionMap, strictAADLMode)) { + for(file <- genPyNodeFiles(modelName, threadComponents, connectionMap, datatypeMap, strictAADLMode, reporter)) { files = files :+ file } From feb3fb8e82d05715ddef66075a7aa476fefc0ebb Mon Sep 17 00:00:00 2001 From: Cat Date: Fri, 14 Feb 2025 13:43:38 -0600 Subject: [PATCH 04/18] Integrated inMsgs and outMsgs --- .../hamr/codegen/ros2/GeneratorPy.scala | 175 ++++++++++++------ 1 file changed, 122 insertions(+), 53 deletions(-) diff --git a/shared/src/main/scala/org/sireum/hamr/codegen/ros2/GeneratorPy.scala b/shared/src/main/scala/org/sireum/hamr/codegen/ros2/GeneratorPy.scala index a52d0717..325cd47f 100644 --- a/shared/src/main/scala/org/sireum/hamr/codegen/ros2/GeneratorPy.scala +++ b/shared/src/main/scala/org/sireum/hamr/codegen/ros2/GeneratorPy.scala @@ -368,13 +368,29 @@ object GeneratorPy { return portCode } + def genPyInfrastructureInQueue(inPort: AadlPort): ST = { + val portName = inPort.identifier + + val inMsgQueue: ST = + st"self.infrastructureIn_${portName} = Queue()" + return inMsgQueue + } + + def genPyApplicationInQueue(inPort: AadlPort): ST = { + val portName = inPort.identifier + + val inMsgQueue: ST = + st"self.applicationIn_${portName} = Queue()" + return inMsgQueue + } + def genPyGetApplicationInValue(inPort: AadlPort, portType: String): ST = { val portName = inPort.identifier // Int32 is a placeholder message value val subscriptionMessageHeader: ST = st"""self.get_${portName}() - | MsgType msg = applicationIn_${portName}.front() + | msg = applicationIn_${portName}.front() | return get<${portType}>(msg) |""" return subscriptionMessageHeader @@ -520,6 +536,30 @@ object GeneratorPy { return subscriptionMessage } + def genPySubscriptionMessageVar(inPort: AadlPort): ST = { + val portName = inPort.identifier + + val subscriptionMessageVar: ST = + st"self.${portName}_msg_holder;" + return subscriptionMessageVar + } + + def genPyInfrastructureOutQueue(inPort: AadlPort): ST = { + val portName = inPort.identifier + + val outMsgQueue: ST = + st"self.infrastructureOut_${portName} = Queue()" + return outMsgQueue + } + + def genPyApplicationOutQueue(inPort: AadlPort): ST = { + val portName = inPort.identifier + + val outMsgQueue: ST = + st"self.applicationOut_${portName} = Queue()" + return outMsgQueue + } + def genPyFileMsgTypeIncludes(packageName: String, msgTypes: ISZ[String]): ISZ[ST] = { var includes: ISZ[ST] = IS() @@ -580,12 +620,13 @@ object GeneratorPy { var tuples: ISZ[String] = IS() for (name <- portNames) { - tuples = tuples :+ s"{&infrastructureIn_${name}, &applicationIn_${name}}" + tuples = tuples :+ s"[self.infrastructureIn_${name}, self.applicationIn_${name}]" } val vector: ST = - st"""inDataPortTupleVector = - | ${(tuples, ",\n")} + st"""inDataPortTupleVector = [ + | ${(tuples, ",\n")} + | ] """ return vector } @@ -594,12 +635,13 @@ object GeneratorPy { var tuples: ISZ[String] = IS() for (name <- portNames) { - tuples = tuples :+ s"{&infrastructureIn_${name}, &applicationIn_${name}}" + tuples = tuples :+ s"[self.infrastructureIn_${name}, self.applicationIn_${name}]" } val vector: ST = - st"""inEventPortTupleVector = - | ${(tuples, ",\n")} + st"""inEventPortTupleVector = [ + | ${(tuples, ",\n")} + | ] """ return vector } @@ -608,12 +650,13 @@ object GeneratorPy { var tuples: ISZ[String] = IS() for (name <- portNames) { - tuples = tuples :+ s"{&applicationOut_${name}, &infrastructureOut_${name}, &self.sendOut_${name}}" + tuples = tuples :+ s"[self.applicationOut_${name}, self.infrastructureOut_${name}, self.sendOut_${name}]" } val vector: ST = - st"""outPortTupleVector = - | ${(tuples, ",\n")} + st"""outPortTupleVector = [ + | ${(tuples, ",\n")} + | ] """ return vector } @@ -637,10 +680,10 @@ object GeneratorPy { | self.enqueue(applicationQueue, eventMsg) | | for port in inDataPortTupleVector: - | infrastructureQueue = std::get<0>(port) + | infrastructureQueue = port[0] | if !infrastructureQueue.empty(): | msg = infrastructureQueue.front() - | self.enqueue(*std::get<1>(port), msg) + | self.enqueue(*port[1], msg) """ return method } @@ -649,17 +692,17 @@ object GeneratorPy { val method: ST = st"""def receiveInputs(self): | for port in inDataPortTupleVector: - | auto infrastructureQueue = std::get<0>(port) + | infrastructureQueue = port[0] | if !infrastructureQueue.empty(): | msg = infrastructureQueue.front() - | self.enqueue(*std::get<1>(port), msg) + | self.enqueue(*port[1], msg) | | for port in inEventPortTupleVector: - | auto infrastructureQueue = std::get<0>(port) - | if !infrastructureQueue.empty(): - | msg = infrastructureQueue.front() - | infrastructureQueue->pop() - | self.enqueue(*std::get<1>(port), msg) + | infrastructureQueue = port[0] + | if !infrastructureQueue.empty(): + | msg = infrastructureQueue.front() + | infrastructureQueue.pop() + | self.enqueue(*port[1], msg) """ return method } @@ -678,18 +721,18 @@ object GeneratorPy { val method: ST = st"""def sendOutputs(self): | for port in outPortTupleVector: - | applicationQueue = std::get<0>(port) + | applicationQueue = port[0] | if applicationQueue.size() != 0: | msg = applicationQueue.front() | applicationQueue.pop() - | enqueue(*std::get<1>(port), msg) + | enqueue(*port[1], msg) | | for port in outPortTupleVector: - | infrastructureQueue = std::get<1>(port) + | infrastructureQueue = port[1] | if infrastructureQueue.size() != 0: | msg = infrastructureQueue.front() | infrastructureQueue.pop() - | (this->*std::get<2>(port))(msg) + | (this->*port[2])(msg) """ return method } @@ -711,6 +754,9 @@ object GeneratorPy { var strictSubscriptionHandlerBaseMethods: ISZ[ST] = IS() var msgTypes: ISZ[String] = IS() + var inMsgVars: ISZ[ST] = IS() + var outMsgVars: ISZ[ST] = IS() + var hasInPorts = F for (p <- component.getPorts()) { val portDatatype: String = genPortDatatype(p, packageName, datatypeMap, reporter) @@ -720,6 +766,8 @@ object GeneratorPy { if (strictAADLMode) { if (p.direction == Direction.In) { subscribers = subscribers :+ genPyTopicSubscriptionStrict(p, isSporadic(component), portDatatype) + inMsgVars = inMsgVars :+ genPyInfrastructureInQueue(p) + inMsgVars = inMsgVars :+ genPyApplicationInQueue(p) if (!isSporadic(component) || p.isInstanceOf[AadlDataPort]) { inTuplePortNames = inTuplePortNames :+ p.identifier subscriptionMessageGetters = subscriptionMessageGetters :+ genPyGetApplicationInValue(p, portDatatype) @@ -753,10 +801,13 @@ object GeneratorPy { subscriberMethods = subscriberMethods :+ genPySubscriptionHandlerPeriodic(p, portDatatype) subscriptionMessageGetters = subscriptionMessageGetters :+ genPyGetSubscriptionMessage(p, nodeName) + inMsgVars = inMsgVars :+ genPySubscriptionMessageVar(p) } hasInPorts = T } else { + outMsgVars = outMsgVars :+ genPyInfrastructureOutQueue(p) + outMsgVars = outMsgVars :+ genPyApplicationOutQueue(p) if (connectionMap.get(p.path).nonEmpty) { val inputPorts = connectionMap.get(p.path).get val inputPortNames = getPortNames(inputPorts) @@ -775,8 +826,7 @@ object GeneratorPy { if (strictAADLMode) { stdIncludes = st"""${stdIncludes} - |#include - |#include + |from typing import Union |import threading""" } @@ -787,9 +837,9 @@ object GeneratorPy { |${(stdIncludes, "\n")} |${(typeIncludes, "\n")} | - |//================================================= - |// D O N O T E D I T T H I S F I L E - |//================================================= + |#================================================= + |# D O N O T E D I T T H I S F I L E + |#================================================= | |class ${nodeName}(Node): | def __init__(self): @@ -798,6 +848,13 @@ object GeneratorPy { | ${genPyCallbackGroupVar()} """ + if (strictAADLMode) { + fileBody = + st"""${fileBody} + | MsgType = Union[${(msgTypes, ", ")}] + """ + } + if (hasInPorts) { fileBody = st"""${fileBody} @@ -807,21 +864,21 @@ object GeneratorPy { fileBody = st"""${fileBody} - | // Setting up connections + | # Setting up connections | ${(subscribers ++ publishers, "\n")}""" if(!isSporadic(component)) { if (strictAADLMode) { fileBody = st"""${fileBody} - | // timeTriggeredCaller callback timer + | # timeTriggeredCaller callback timer | ${genPyTimeTriggeredStrict(nodeName, component)} """ } else { fileBody = st"""${fileBody} - | // timeTriggered callback timer + | # timeTriggered callback timer | ${genPyTimeTriggeredTimer(nodeName, component)} """ } @@ -830,30 +887,44 @@ object GeneratorPy { if(strictAADLMode) { fileBody = st"""${fileBody} - | // Used by receiveInputs + | # Used by receiveInputs | ${genPyInDataPortTupleVector(inTuplePortNames)}""" if (!isSporadic(component)) { fileBody = st"""${fileBody} - | // Used by receiveInputs + | # Used by receiveInputs | ${genPyInEventPortTupleVector(inTuplePortNames)}""" } fileBody = st"""${fileBody} - | // Used by sendOutputs + | # Used by sendOutputs | ${genPyOutPortTupleVector(nodeName, outPortNames)}""" } if (subscriberMethods.size > 0 || publisherMethods.size > 0) { fileBody = st"""${fileBody} - |//================================================= - |// C o m m u n i c a t i o n - |//================================================= + |#================================================= + |# C o m m u n i c a t i o n + |#================================================= """ + if (inMsgVars.size > 0) { + fileBody = + st"""${fileBody} + | ${(inMsgVars, "\n")} + """ + } + + if (outMsgVars.size > 0) { + fileBody = + st"""${fileBody} + | ${(outMsgVars, "\n")} + """ + } + if (subscriberMethods.size > 0) { fileBody = st"""${fileBody} @@ -909,7 +980,7 @@ object GeneratorPy { val subscriptionHandlerHeader: ST = st"""def handle_${handlerName}(self, msg) |{ - | // Handle ${handlerName} msg + | # Handle ${handlerName} msg |} """ return subscriptionHandlerHeader @@ -922,7 +993,7 @@ object GeneratorPy { val subscriptionHandlerHeader: ST = st"""def handle_${handlerName}(self, msg) |{ - | // Handle ${handlerName} msg + | # Handle ${handlerName} msg |} """ return subscriptionHandlerHeader @@ -932,7 +1003,7 @@ object GeneratorPy { val timeTriggered: ST = st"""def timeTriggered(self) |{ - | // Handle communication + | # Handle communication |} """ return timeTriggered @@ -966,19 +1037,19 @@ object GeneratorPy { st"""#!/usr/bin/env python3 |import rclpy |from rclpy.node import Node - |//================================================= - |// I n i t i a l i z e E n t r y P o i n t - |//================================================= + |#================================================= + |# I n i t i a l i z e E n t r y P o i n t + |#================================================= |def initialize(self) |{ | self.get_logger().info("Initialize Entry Point invoked"); | - | // Initialize the node + | # Initialize the node |} | - |//================================================= - |// C o m p u t e E n t r y P o i n t - |//================================================= + |#================================================= + |# C o m p u t e E n t r y P o i n t + |#================================================= |${(subscriptionHandlers, "\n")} """ @@ -1041,7 +1112,8 @@ object GeneratorPy { py_files :+ genPyBaseNodePyFile(top_level_package_nameT, comp, connectionMap, datatypeMap, strictAADLMode, reporter) py_files = py_files :+ genPyUserNodePyFile(top_level_package_nameT, comp, strictAADLMode) - py_files :+ genPyNodeRunnerFile(top_level_package_nameT, comp) + py_files = + py_files :+ genPyNodeRunnerFile(top_level_package_nameT, comp) } return py_files } @@ -1055,12 +1127,9 @@ object GeneratorPy { datatypeMap: Map[AadlType, (String, ISZ[String])], strictAADLMode: B, reporter: Reporter): ISZ[(ISZ[String], ST)] = { var files: ISZ[(ISZ[String], ST)] = IS() - files = files :+ genPyFormatLaunchFile(modelName, threadComponents) + //files = files :+ genPyFormatLaunchFile(modelName, threadComponents) files = files :+ genPySetupFile(modelName, threadComponents) - - for(file <- genPyNodeFiles(modelName, threadComponents, connectionMap, datatypeMap, strictAADLMode, reporter)) { - files = files :+ file - } + files = files ++ genPyNodeFiles(modelName, threadComponents, connectionMap, datatypeMap, strictAADLMode, reporter) return files } From 7794ecb2d838206c34e4c29b6eb49a690321b651 Mon Sep 17 00:00:00 2001 From: Cat Date: Thu, 27 Feb 2025 15:40:02 -0600 Subject: [PATCH 05/18] Added tests for python and refactoring GenPy --- .../hamr/codegen/ros2/GeneratorPy.scala | 1028 +++++++++++++---- .../hamr/codegen/ros2/Ros2Codegen.scala | 10 +- 2 files changed, 814 insertions(+), 224 deletions(-) diff --git a/shared/src/main/scala/org/sireum/hamr/codegen/ros2/GeneratorPy.scala b/shared/src/main/scala/org/sireum/hamr/codegen/ros2/GeneratorPy.scala index 325cd47f..e62e77d0 100644 --- a/shared/src/main/scala/org/sireum/hamr/codegen/ros2/GeneratorPy.scala +++ b/shared/src/main/scala/org/sireum/hamr/codegen/ros2/GeneratorPy.scala @@ -3,11 +3,12 @@ package org.sireum.hamr.codegen.ros2 import org.sireum._ +import org.sireum.hamr.codegen.common.containers.Marker import org.sireum.hamr.codegen.common.symbols.{AadlDataPort, AadlEventDataPort, AadlPort, AadlThread, Dispatch_Protocol} -import org.sireum.hamr.codegen.common.types.AadlType +import org.sireum.hamr.codegen.common.types.{AadlType, EnumType} import org.sireum.hamr.ir.Direction import org.sireum.message.Reporter -import org.sireum.ops.ISZOps +import org.sireum.ops.{ISZOps, StringOps} object GeneratorPy { @@ -56,10 +57,40 @@ object GeneratorPy { return component.dispatchProtocol == Dispatch_Protocol.Sporadic } + def isEventPort(portType: String): B = { + return ops.StringOps(portType).substring(portType.size - 7, portType.size) == "::Empty" + } + + def genNodeName(component: AadlThread): String = { + var name: ST = st"" + var i: Z = 1 + while (i < component.path.size) { + name = st"${name}_${component.path.apply(i)}" + i = i + 1 + } + return ops.StringOps(name.render).substring(1, name.render.size) + } + + def genPortName(port: AadlPort): String = { + var name: ST = st"" + var i: Z = 1 + while (i < port.path.size) { + name = st"${name}_${port.path.apply(i)}" + i = i + 1 + } + return ops.StringOps(name.render).substring(1, name.render.size) + } + def getPortNames(portNames: ISZ[ISZ[String]]): ISZ[String] = { var names: ISZ[String] = IS() - for (name <- portNames) { - names = names :+ seqToString(name, "_") + for (portName <- portNames) { + var name: ST = st"" + var i: Z = 1 + while (i < portName.size) { + name = st"${name}_${portName.apply(i)}" + i = i + 1 + } + names = names :+ ops.StringOps(name.render).substring(1, name.render.size) } return names } @@ -94,7 +125,7 @@ object GeneratorPy { for (s <- seq) { str = s"$str$s$separator" } - str = ops.StringOps(str).substring(0, str.size - 1) + //str = ops.StringOps(str).substring(0, str.size - 1) return str } @@ -120,7 +151,7 @@ object GeneratorPy { // Setup file for node source package // Example: https://github.com/santoslab/ros-examples/blob/main/tempControl_ws/src/tc_py_pkg/setup.py - def genPySetupFile(modelName: String, threadComponents: ISZ[AadlThread]): (ISZ[String], ST) = { + def genPySetupFile(modelName: String, threadComponents: ISZ[AadlThread]): (ISZ[String], ST, B, ISZ[Marker]) = { val top_level_package_nameT: String = genPyPackageName(modelName) val fileName: String = "setup.py" @@ -165,7 +196,7 @@ object GeneratorPy { val filePath: ISZ[String] = IS("src", top_level_package_nameT, fileName) - return (filePath, setupFileBody) + return (filePath, setupFileBody, true, IS()) } def genPackageFilePkgDependencies(packages: ISZ[String]): ISZ[ST] = { @@ -178,6 +209,207 @@ object GeneratorPy { return requirements } + // Setup file for node source package + // Example: https://github.com/santoslab/ros-examples/blob/main/tempControl_ws/src/tc_py_pkg/setup.cfg + def genCfgSetupFile(modelName: String): (ISZ[String], ST, B, ISZ[Marker]) = { + val top_level_package_nameT: String = genPyPackageName(modelName) + val fileName: String = "setup.cfg" + + val setupFileBody = + st"""# ${fileName} in src/${top_level_package_nameT} + |[develop] + |script_dir=$$base/lib/${top_level_package_nameT} + |[install] + |install_scripts=$$base/lib/${top_level_package_nameT} + """ + + val filePath: ISZ[String] = IS("src", top_level_package_nameT, fileName) + + return (filePath, setupFileBody, true, IS()) + } + + def genXmlPackageFile(modelName: String): (ISZ[String], ST, B, ISZ[Marker]) = { + val top_level_package_nameT: String = genPyPackageName(modelName) + val fileName: String = "package.xml" + + val packages: ISZ[String] = IS("example_interfaces") + val pkgDependencies: ISZ[ST] = genPackageFilePkgDependencies(packages) + + val setupFileBody = + st""" + | + | + | ${top_level_package_nameT} + | 0.0.0 + | TODO: Package description + | ed + | TODO: License declaration + | + | rclpy + | ${(pkgDependencies, "\n")} + | + | ament_copyright + | ament_flake8 + | ament_pep257 + | python3-pytest + | + | + | ament_python + | + | + """ + + val filePath: ISZ[String] = IS("src", top_level_package_nameT, fileName) + + return (filePath, setupFileBody, true, IS()) + } + + def genPyInitFile(packageName: String): (ISZ[String], ST, B, ISZ[Marker]) = { + val fileName = genPyNodeSourceName("__init__") + + val fileBody = + st""" + """ + + val filePath: ISZ[String] = IS("src", packageName, packageName, fileName) + + return (filePath, fileBody, true, IS()) + } + + def genPySubInitFile(modelName: String, subModelName: String): (ISZ[String], ST, B, ISZ[Marker]) = { + val top_level_package_nameT: String = genPyPackageName(modelName) + val fileName: String = "__init__.py" + + val setupFileBody = + st""" + """ + + val filePath: ISZ[String] = IS("src", top_level_package_nameT, top_level_package_nameT, subModelName, fileName) + + return (filePath, setupFileBody, true, IS()) + } + + def genPyResourceFile(modelName: String): (ISZ[String], ST, B, ISZ[Marker]) = { + val top_level_package_nameT: String = genPyPackageName(modelName) + + val setupFileBody = + st""" + """ + + val filePath: ISZ[String] = IS("src", top_level_package_nameT, "resource", top_level_package_nameT) + + return (filePath, setupFileBody, true, IS()) + } + + def genPyCopyrightFile(modelName: String): (ISZ[String], ST, B, ISZ[Marker]) = { + val top_level_package_nameT: String = genPyPackageName(modelName) + val fileName: String = "test_copyright.py" + + val setupFileBody = + st"""# Copyright 2015 Open Source Robotics Foundation, Inc. + |# + |# 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. + | + |from ament_copyright.main import main + |import pytest + | + | + |# Remove the `skip` decorator once the source file(s) have a copyright header + |@pytest.mark.skip(reason='No copyright header has been placed in the generated source file.') + |@pytest.mark.copyright + |@pytest.mark.linter + |def test_copyright(): + | rc = main(argv=['.', 'test']) + | assert rc == 0, 'Found errors' + """ + + val filePath: ISZ[String] = IS("src", top_level_package_nameT, "test", fileName) + + return (filePath, setupFileBody, true, IS()) + } + + def genPyFlakeFile(modelName: String): (ISZ[String], ST, B, ISZ[Marker]) = { + val top_level_package_nameT: String = genPyPackageName(modelName) + val fileName: String = "test_flake8.py" + + val setupFileBody = + st"""# Copyright 2017 Open Source Robotics Foundation, Inc. + |# + |# 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. + | + |from ament_flake8.main import main_with_errors + |import pytest + | + | + |@pytest.mark.flake8 + |@pytest.mark.linter + |def test_flake8(): + | rc, errors = main_with_errors(argv=[]) + | assert rc == 0, \ + | 'Found %d code style errors / warnings:\n' % len(errors) + \ + | '\n'.join(errors) + """ + + val filePath: ISZ[String] = IS("src", top_level_package_nameT, "test", fileName) + + return (filePath, setupFileBody, true, IS()) + } + + def genPyPrepFile(modelName: String): (ISZ[String], ST, B, ISZ[Marker]) = { + val top_level_package_nameT: String = genPyPackageName(modelName) + val fileName: String = "test_prep257.py" + + val setupFileBody = + st"""# Copyright 2015 Open Source Robotics Foundation, Inc. + |# + |# 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. + | + |from ament_pep257.main import main + |import pytest + | + | + |@pytest.mark.linter + |@pytest.mark.pep257 + |def test_pep257(): + | rc = main(argv=['.', 'test']) + | assert rc == 0, 'Found code style errors / warnings' + """ + + val filePath: ISZ[String] = IS("src", top_level_package_nameT, "test", fileName) + + return (filePath, setupFileBody, true, IS()) + } + //================================================ // L a u n c h File Setup Files //================================================ @@ -277,8 +509,8 @@ object GeneratorPy { val s = st""" |${launch_node_decl_nameT} = Node( - | package = ${top_level_package_nameT}, - | executable = ${node_executable_file_nameT} + | package = ${top_level_package_nameT}, + | executable = ${node_executable_file_nameT} | ) """ return s @@ -324,6 +556,100 @@ object GeneratorPy { return (filePath, launchFileBody) } + //================================================ + // I n t e r f a c e s Setup Files + //================================================ + // ROS2 data/message types are defined in a "{package_name}_interfaces" package according to convention + // The "Empty" datatype, which has no data fields, is used for event ports + + def genMsgFiles(modelName: String, datatypeMap: Map[AadlType, (String, ISZ[String])]): ISZ[(ISZ[String], ST, B, ISZ[Marker])] = { + var msg_files: ISZ[(ISZ[String], ST, B, ISZ[Marker])] = IS() + for (datatype <- datatypeMap.entries) { + msg_files = msg_files :+ genMsgFile(modelName, datatype._2._1, datatype._2._2) + } + msg_files = msg_files :+ (ISZ("src", s"${genPyPackageName(modelName)}_interfaces", "msg", "Empty.msg"), st"", T, IS()) + return msg_files + } + + def genMsgFile(modelName: String, datatypeName: String, datatypeContent: ISZ[String]): (ISZ[String], ST, B, ISZ[Marker]) = { + val top_level_package_nameT: String = genPyPackageName(modelName) + + val fileBody = st"${(datatypeContent, "\n")}" + + val filePath: ISZ[String] = IS("src", s"${top_level_package_nameT}_interfaces", "msg", s"${datatypeName}.msg") + + return (filePath, fileBody, T, IS()) + } + + def genInterfacesCMakeListsFile(modelName: String, datatypeMap: Map[AadlType, (String, ISZ[String])]): (ISZ[String], ST, B, ISZ[Marker]) = { + val top_level_package_nameT: String = genPyPackageName(modelName) + val fileName: String = "CMakeLists.txt" + var msgTypes: ISZ[String] = IS() + for (msg <- datatypeMap.valueSet.elements) { + msgTypes = msgTypes :+ s"msg/${msg._1}.msg" + } + msgTypes = msgTypes :+ s"msg/Empty.msg" + + val setupFileBody = + st"""cmake_minimum_required(VERSION 3.8) + |project(${top_level_package_nameT}_interfaces) + | + |if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + | add_compile_options(-Wall -Wextra -Wpedantic) + |endif() + | + |find_package(ament_cmake REQUIRED) + | + |find_package(rosidl_default_generators REQUIRED) + | + |rosidl_generate_interfaces($${PROJECT_NAME} + | ${(msgTypes, "\n")} + |) + | + |ament_export_dependencies(rosidl_default_runtime) + | + |ament_package() + """ + + val filePath: ISZ[String] = IS("src", s"${top_level_package_nameT}_interfaces", fileName) + + return (filePath, setupFileBody, T, IS()) + } + + def genInterfacesPackageFile(modelName: String): (ISZ[String], ST, B, ISZ[Marker]) = { + val top_level_package_nameT: String = genPyPackageName(modelName) + val fileName: String = "package.xml" + + val setupFileBody = + st""" + | + | + | ${top_level_package_nameT}_interfaces + | 0.0.0 + | TODO: Package description + | sireum + | TODO: License declaration + | + | ament_cmake + | + | rosidl_default_generators + | rosidl_default_runtime + | rosidl_interface_packages + | + | ament_lint_auto + | ament_lint_common + | + | + | ament_cmake + | + | + """ + + val filePath: ISZ[String] = IS("src", s"${top_level_package_nameT}_interfaces", fileName) + + return (filePath, setupFileBody, T, IS()) + } + //================================================ // Node files (Py) // Example: https://github.com/santoslab/ros-examples/tree/main/tempControl_ws/src/tc_py_pkg/tc_py_pkg @@ -335,37 +661,46 @@ object GeneratorPy { // 'fanCmd', // self.handle_fanCmd, // 10) - def genPyTopicSubscriptionStrict(inPort: AadlPort, isSporadic: B, portType: String): ST = { - val topicName = seqToString(inPort.path, "_") - val portName = inPort.identifier + def genPyTopicSubscriptionStrict(inPort: AadlPort, portType: String, outPortNames: ISZ[String]): ST = { + val portName = genPortName(inPort) + val handlerName = inPort.identifier - val handler: ST = - if (!isSporadic || inPort.isInstanceOf[AadlDataPort]) { - st"self.enqueue(infrastructureIn_${portName}, msg)" - } - else { - // TODO: CPP to Py - st"""self.enqueue(infrastructureIn_${portName}, msg); - |std::thread([this]() { - | threading.Lock() - | self.receiveInputs(infrastructureIn_${portName}, applicationIn_${portName}); - | if (applicationIn_${portName}.empty()) return; - | self.handle_${portName}_base(applicationIn_${portName}.front()); - | applicationIn_${portName}.pop(); - | self.sendOutputs(); - |}).detach();""" - } + val handler: ST = st"self.accept_${handlerName}" - // Int32 is a placeholder message value - val portCode: ST = - st"""self.${topicName}_subscription_ = self.create_subscription( - | ${portType}, - | "${topicName}", - | self.${handler}, - | 1, - | callback_group=self.${subscription_options_name}) - |""" - return portCode + if (outPortNames.size == 1) { + val topicName = outPortNames.apply(0) + + val portCode: ST = + st"""self.${portName}_subscription_ = self.create_subscription( + | ${portType}, + | "${topicName}", + | self.${handler}, + | 1, + | callback_group=self.${subscription_options_name}); + """ + return portCode + } + + // If the port is a fan in port + var inputInstances: ISZ[ST] = IS() + var counter = 1 + + for (outPortName <- outPortNames) { + inputInstances = inputInstances :+ + st"""self.${portName}_subscription_${counter} = self.create_subscription( + | ${portType}, + | "${outPortName}", + | self.${handler}, + | 1, + | callback_group=self.${subscription_options_name}); + """ + counter = counter + 1 + } + + val fanPortCode: ST = + st"${(inputInstances, "\n")}" + + return fanPortCode } def genPyInfrastructureInQueue(inPort: AadlPort): ST = { @@ -389,9 +724,9 @@ object GeneratorPy { // Int32 is a placeholder message value val subscriptionMessageHeader: ST = - st"""self.get_${portName}() - | msg = applicationIn_${portName}.front() - | return get<${portType}>(msg) + st"""def get_${portName}(self): + | msg = applicationIn_${portName}.front() + | return get(msg) |""" return subscriptionMessageHeader } @@ -399,14 +734,24 @@ object GeneratorPy { def genPySubscriptionHandlerBaseSporadic(inPort: AadlPort, portType: String): ST = { val handlerName = inPort.identifier - // Int32 is a placeholder message value - val handlerCode: ST = - st"""def handle_${handlerName}_base(self, msg): - | if isinstance(get_if<${portType}>(&msg), Int32): - | handle_${handlerName}(*typedMsg) - | else: - | this.get_logger().error("Sending out wrong type of variable on port ${handlerName}.\nThis shouldn't be possible. If you are seeing this message, please notify this tool's current maintainer.") - |""" + var handlerCode: ST = st"" + if (isEventPort(portType)) { + handlerCode = + st"""def handle_${handlerName}_base(self, msg): + | self.handle_${handlerName}() + |""" + } + else { + handlerCode = + st"""void handle_${handlerName}_base(self, msg): + | if isInstance(msg, ${portType}): + | typedMsg = ${portType}() + | typedMsg.data = msg + | handle_${handlerName}(typedMsg) + | else: + | self.get_logger.error("Receiving wrong type of variable on port ${handlerName}.\nThis shouldn't be possible. If you are seeing this message, please notify this tool's current maintainer.") + |""" + } return handlerCode } @@ -417,7 +762,7 @@ object GeneratorPy { // 10, // callback_group=self.${callback_group_name}) def genPyTopicPublisher(outPort: AadlPort, portType: String, inPortNames: ISZ[String]): ST = { - val portName = seqToString(outPort.path, "_") + val portName = genPortName(outPort) if (inPortNames.size == 1) { val inPortName = inPortNames.apply(0) @@ -425,9 +770,9 @@ object GeneratorPy { // Int32 is a placeholder message value val portCode: ST = st"""self.${portName}_publisher_ = self.create_publisher( - | ${portType}, - | "${inPortName}", - | 1) + | ${portType}, + | "${inPortName}", + | 1) |""" return portCode } @@ -439,11 +784,11 @@ object GeneratorPy { for (inPortName <- inPortNames) { outputInstances = outputInstances :+ st"""self.${portName}_publisher_${counter} = self.create_publisher( - | ${portType} - | "${inPortName}", - | 1) + | ${portType}, + | "${inPortName}", + | 1) |""" - counter = counter + 1; + counter = counter + 1 } val fanPortCode: ST = @@ -453,40 +798,53 @@ object GeneratorPy { } def genPyTopicPublishMethodStrict(outPort: AadlPort, portType: String, inputPortCount: Z): ST = { - val portName = seqToString(outPort.path, "_") + val portName = genPortName(outPort) val handlerName = outPort.identifier var publishers: ISZ[ST] = IS() if (inputPortCount == 1) { publishers = publishers :+ - st"self.${portName}_publisher_.publish(*typedMsg)" + st"self.${portName}_publisher_.publish(typedMsg)" } else { for (i <- 1 to inputPortCount) { publishers = publishers :+ - st"self.${portName}_publisher_${i}.publish(*typedMsg)" + st"self.${portName}_publisher_${i}.publish(typedMsg)" } } // Int32 is a placeholder message value val publisherCode: ST = st"""def sendOut_${handlerName}(self, msg): - | if isinstance(get_if<${portType}>(&msg), Int32): - | ${(publishers, "\n")} - | else: - | this.get_logger().error("Sending out wrong type of variable on port ${handlerName}.\nThis shouldn't be possible. If you are seeing this message, please notify this tool's current maintainer.") + | if isinstance(msg, Int32): + | typedMsg = ${portType}() + | typedMsg.data = msg + | ${(publishers, "\n")} + | else: + | this.get_logger().error("Sending out wrong type of variable on port ${handlerName}.\nThis shouldn't be possible. If you are seeing this message, please notify this tool's current maintainer.") |""" return publisherCode } - def genPyPutMsgMethodStrict(outPort: AadlPort): ST = { + def genPyPutMsgMethodStrict(outPort: AadlPort, portType: String): ST = { val handlerName = outPort.identifier - // Int32 is a placeholder message value - val putMsgCode: ST = - st"""def put_${handlerName}(self, msg): - | self.enqueue(applicationOut_${handlerName}, msg) - |""" + var putMsgCode: ST = st"" + + if (isEventPort(portType)) { + putMsgCode = + st"""def put_${handlerName}(self, msg): + | self.enqueue(applicationOut_${handlerName}, ${portType}()) + |""" + } + else { + putMsgCode = + st"""def put_${handlerName}(self, msg): + | typedMsg = ${portType}() + | typedMsg.data = msg + | self.enqueue(applicationOut_${handlerName}, typedMsg) + |""" + } return putMsgCode } @@ -496,20 +854,63 @@ object GeneratorPy { // 'fanCmd', // self.handle_fanCmd, // 10) - def genPyTopicSubscription(inPort: AadlPort, portType: String): ST = { - val topicName = seqToString(inPort.path, "_") - val portName = inPort.identifier + def genPyTopicSubscription(inPort: AadlPort, portType: String, outPortNames: ISZ[String]): ST = { + val portName = genPortName(inPort) + val handlerName = inPort.identifier - // Int32 in a placeholder message value - val portCode: ST = - st"""self.${topicName}_subscription_ = self.create_subscription( - | ${portType}, - | "${topicName}", - | self.handle_${portName}, - | 1, - | callback_group=self.${subscription_options_name} - |""" - return portCode + var handler: ST = st"" + + if(isEventPort(portType)) { + handler = st"self.event_handle_${handlerName}" + } + else { + handler = st"self.handle_${handlerName}" + } + + if (outPortNames.size == 1) { + val topicName = outPortNames.apply(0) + val portCode: ST = + st"""self.${portName}_subscription_ = self.create_subscription( + | ${portType}, + | "${topicName}", + | ${handler}, + | 1, + | callback_group=self.${subscription_options_name}) + |""" + return portCode + } + + // If the port is a fan in port + var inputInstances: ISZ[ST] = IS() + var counter = 1 + + for (outPortName <- outPortNames) { + inputInstances = inputInstances :+ + st"""self.${portName}_subscription_${counter} = self.create_subscription( + | ${portType}, + | "${outPortName}", + | ${handler}, + | 1, + | callback_group=self.${subscription_options_name}) + |""" + counter = counter + 1 + } + + val fanPortCode: ST = + st"${(inputInstances, "\n")}" + + return fanPortCode + } + + def genPyEventPortHandler(inPort: AadlPort, portType: String): ST = { + val handlerName = inPort.identifier + + val handler: ST = + st"""def event_handle_${handlerName}(self, msg): + | handle_${handlerName}() + |""" + + return handler } def genPySubscriptionHandlerPeriodic(inPort: AadlPort, portType: String): ST = { @@ -518,9 +919,9 @@ object GeneratorPy { // Int32 is a placeholder message value val subscriptionHandlerHeader: ST = st"""def handle_${handlerName}(self, msg): - | msgNew = ${portType}() - | msgNew = msg.data - | self.${handlerName}_msg_holder = msgNew + | typedMsg = ${portType}() + | typedMsg.data = msg + | self.${handlerName}_msg_holder = typedMsg |""" return subscriptionHandlerHeader } @@ -530,8 +931,8 @@ object GeneratorPy { // Int32 is a placeholder message value val subscriptionMessage: ST = - st"""def get_${portName}(): - | return self.${portName}_msg_holder + st"""def get_${portName}(self): + | return self.${portName}_msg_holder |""" return subscriptionMessage } @@ -540,7 +941,7 @@ object GeneratorPy { val portName = inPort.identifier val subscriptionMessageVar: ST = - st"self.${portName}_msg_holder;" + st"self.${portName}_msg_holder" return subscriptionMessageVar } @@ -570,27 +971,40 @@ object GeneratorPy { return includes } - def genPyTopicPublishMethod(outPort: AadlPort, nodeName: String, inputPortCount: Z): ST = { - val portName = seqToString(outPort.path, "_") + def genPyTopicPublishMethod(outPort: AadlPort, portType: String, inputPortCount: Z): ST = { + val portName = genPortName(outPort) val handlerName = outPort.identifier var publishers: ISZ[ST] = IS() if (inputPortCount == 1) { publishers = publishers :+ - st"self.${portName}_publisher_.publish(msg)" + st"self.${portName}_publisher_.publish(typedMsg)" } else { for (i <- 1 to inputPortCount) { publishers = publishers :+ - st"${portName}_publisher_${i}.publish(msg)" + st"${portName}_publisher_${i}.publish(typedMsg)" } } - // Int32 is a placeholder message value - val publisherCode: ST = - st"""def put_${handlerName}(self, msg): - | ${(publishers, "\n")} - |""" + var publisherCode: ST = st"" + + if (isEventPort(portType)) { + publisherCode = + st"""def put_${handlerName}(self): + | typedMsg = ${portType}() + | + | ${(publishers, "\n")} + |""" + } + else { + publisherCode = + st"""def put_${handlerName}(self, msg) + | typedMsg = ${portType}() + | typedMsg.data = msg + | ${(publishers, "\n")} + |""" + } return publisherCode } @@ -600,7 +1014,7 @@ object GeneratorPy { return callbackGroup } - def genPyTimeTriggeredStrict(nodeName: String, component: AadlThread): ST = { + def genPyTimeTriggeredStrict(component: AadlThread): ST = { val period = component.period.get val timer: ST = @@ -608,7 +1022,7 @@ object GeneratorPy { return timer } - def genPyTimeTriggeredTimer(nodeName: String, component: AadlThread): ST = { + def genPyTimeTriggeredTimer(component: AadlThread): ST = { val period = component.period.get val timer: ST = @@ -625,7 +1039,7 @@ object GeneratorPy { val vector: ST = st"""inDataPortTupleVector = [ - | ${(tuples, ",\n")} + | ${(tuples, ",\n")} | ] """ return vector @@ -640,13 +1054,13 @@ object GeneratorPy { val vector: ST = st"""inEventPortTupleVector = [ - | ${(tuples, ",\n")} + | ${(tuples, ",\n")} | ] """ return vector } - def genPyOutPortTupleVector(nodeName: String, portNames: ISZ[String]): ST = { + def genPyOutPortTupleVector(portNames: ISZ[String]): ST = { var tuples: ISZ[String] = IS() for (name <- portNames) { @@ -655,91 +1069,92 @@ object GeneratorPy { val vector: ST = st"""outPortTupleVector = [ - | ${(tuples, ",\n")} + | ${(tuples, ",\n")} | ] """ return vector } - def genPyTimeTriggeredCaller(nodeName: String): ST = { + def genPyTimeTriggeredCaller(): ST = { val timeTriggered: ST = st"""def timeTriggeredCaller(self): - | self.receiveInputs() - | timeTriggered() - | self.sendOutputs() + | self.receiveInputs() + | timeTriggered() + | self.sendOutputs() """ return timeTriggered } - def genPyReceiveInputsSporadic(nodeName: String): ST = { + def genPyReceiveInputsSporadic(): ST = { val method: ST = st"""def receiveInputs(self, infrastructureQueue, applicationQueue): | if !infrastructureQueue.empty(): - | eventMsg = infrastructureQueue.front() - | infrastructureQueue.pop() - | self.enqueue(applicationQueue, eventMsg) + | eventMsg = infrastructureQueue.front() + | infrastructureQueue.pop() + | self.enqueue(applicationQueue, eventMsg) | | for port in inDataPortTupleVector: - | infrastructureQueue = port[0] - | if !infrastructureQueue.empty(): - | msg = infrastructureQueue.front() - | self.enqueue(*port[1], msg) + | infrastructureQueue = port[0] + | if !infrastructureQueue.empty(): + | msg = infrastructureQueue.front() + | self.enqueue(port[1], msg) """ return method } - def genPyReceiveInputsPeriodic(nodeName: String): ST = { + def genPyReceiveInputsPeriodic(): ST = { val method: ST = st"""def receiveInputs(self): | for port in inDataPortTupleVector: - | infrastructureQueue = port[0] - | if !infrastructureQueue.empty(): - | msg = infrastructureQueue.front() - | self.enqueue(*port[1], msg) + | infrastructureQueue = port[0] + | if !infrastructureQueue.empty(): + | msg = infrastructureQueue.front() + | self.enqueue(*port[1], msg) | | for port in inEventPortTupleVector: - | infrastructureQueue = port[0] - | if !infrastructureQueue.empty(): - | msg = infrastructureQueue.front() - | infrastructureQueue.pop() - | self.enqueue(*port[1], msg) + | infrastructureQueue = port[0] + | if !infrastructureQueue.empty(): + | msg = infrastructureQueue.front() + | infrastructureQueue.pop() + | self.enqueue(port[1], msg) """ return method } - def genPyEnqueue(nodeName: String): ST = { + def genPyEnqueue(): ST = { val method: ST = st"""def enqueue(self, queue, val): - | if queue.size() >= 1: - | queue.pop() - | queue.push(val) + | if queue.size() >= 1: + | queue.pop() + | queue.push(val) """ return method } - def genPySendOutputs(nodeName: String): ST = { + def genPySendOutputs(): ST = { val method: ST = st"""def sendOutputs(self): | for port in outPortTupleVector: - | applicationQueue = port[0] - | if applicationQueue.size() != 0: - | msg = applicationQueue.front() - | applicationQueue.pop() - | enqueue(*port[1], msg) + | applicationQueue = port[0] + | if applicationQueue.size() != 0: + | msg = applicationQueue.front() + | applicationQueue.pop() + | enqueue(port[1], msg) | | for port in outPortTupleVector: - | infrastructureQueue = port[1] - | if infrastructureQueue.size() != 0: - | msg = infrastructureQueue.front() - | infrastructureQueue.pop() - | (this->*port[2])(msg) + | infrastructureQueue = port[1] + | if infrastructureQueue.size() != 0: + | msg = infrastructureQueue.front() + | infrastructureQueue.pop() + | (sport[2])(msg) """ return method } def genPyBaseNodePyFile(packageName: String, component: AadlThread, connectionMap: Map[ISZ[String], ISZ[ISZ[String]]], - datatypeMap: Map[AadlType, (String, ISZ[String])], strictAADLMode: B, reporter: Reporter): (ISZ[String], ST) = { - val nodeName = s"${component.pathAsString("_")}_base" + datatypeMap: Map[AadlType, (String, ISZ[String])], strictAADLMode: B, + invertTopicBinding: B, reporter: Reporter): (ISZ[String], ST, B, ISZ[Marker]) = { + val nodeName = s"${genNodeName(component)}_base" val fileName = genPyNodeSourceName(nodeName) var subscribers: ISZ[ST] = IS() @@ -747,10 +1162,12 @@ object GeneratorPy { var subscriberMethods: ISZ[ST] = IS() var publisherMethods: ISZ[ST] = IS() var subscriptionMessageGetters: ISZ[ST] = IS() + var eventPortHandlers: ISZ[ST] = IS() var outPortNames: ISZ[String] = IS() - var inTuplePortNames: ISZ[String] = IS() + var inPortNames: ISZ[String] = IS() var strictPutMsgMethods: ISZ[ST] = IS() + var strictSubscriptionMessageAcceptorMethods: ISZ[ST] = IS() var strictSubscriptionHandlerBaseMethods: ISZ[ST] = IS() var msgTypes: ISZ[String] = IS() @@ -765,11 +1182,26 @@ object GeneratorPy { } if (strictAADLMode) { if (p.direction == Direction.In) { - subscribers = subscribers :+ genPyTopicSubscriptionStrict(p, isSporadic(component), portDatatype) + if (invertTopicBinding) { + if (connectionMap.get(p.path).nonEmpty) { + val outputPorts = connectionMap.get(p.path).get + val outputPortNames = getPortNames(outputPorts) + subscribers = subscribers :+ genPyTopicSubscriptionStrict(p, portDatatype, outputPortNames) + } + else { + subscribers = subscribers :+ genPyTopicSubscriptionStrict(p, portDatatype, getPortNames(IS(p.path.toISZ))) + } + } + else { + subscribers = subscribers :+ genPyTopicSubscriptionStrict(p, portDatatype, getPortNames(IS(p.path.toISZ))) + } + + //TODO: MessageAcceptors inMsgVars = inMsgVars :+ genPyInfrastructureInQueue(p) inMsgVars = inMsgVars :+ genPyApplicationInQueue(p) + if (!isSporadic(component) || p.isInstanceOf[AadlDataPort]) { - inTuplePortNames = inTuplePortNames :+ p.identifier + inPortNames = inPortNames :+ p.identifier subscriptionMessageGetters = subscriptionMessageGetters :+ genPyGetApplicationInValue(p, portDatatype) } else { @@ -780,23 +1212,50 @@ object GeneratorPy { } else { outPortNames = outPortNames :+ p.identifier - if (connectionMap.get(p.path).nonEmpty) { - val inputPorts = connectionMap.get(p.path).get - val inputPortNames = getPortNames(inputPorts) - publishers = publishers :+ genPyTopicPublisher(p, portDatatype, inputPortNames) - publisherMethods = publisherMethods :+ - genPyTopicPublishMethodStrict(p, portDatatype, inputPortNames.size) + if (invertTopicBinding) { + publishers = publishers :+ genPyTopicPublisher(p, portDatatype, getPortNames(IS(p.path.toISZ))) + genPyTopicPublishMethodStrict(p, portDatatype, 1) } else { - publisherMethods = publisherMethods :+ - genPyTopicPublishMethodStrict(p, portDatatype, 0) + if (connectionMap.get(p.path).nonEmpty) { + val inputPorts = connectionMap.get(p.path).get + val inputPortNames = getPortNames(inputPorts) + publishers = publishers :+ genPyTopicPublisher(p, portDatatype, inputPortNames) + publisherMethods = publisherMethods :+ + genPyTopicPublishMethodStrict(p, portDatatype, inputPortNames.size) + } + else { + // Out ports with no connections should still publish to a topic + publishers = publishers :+ genPyTopicPublisher(p, portDatatype, getPortNames(IS(p.path.toISZ))) + publisherMethods = publisherMethods :+ + genPyTopicPublishMethodStrict(p, portDatatype, 1) + } } - strictPutMsgMethods = strictPutMsgMethods :+ genPyPutMsgMethodStrict(p) + strictPutMsgMethods = strictPutMsgMethods :+ genPyPutMsgMethodStrict(p, portDatatype) } } else { if (p.direction == Direction.In) { - subscribers = subscribers :+ genPyTopicSubscription(p, portDatatype) + if (invertTopicBinding) { + if (connectionMap.get(p.path).nonEmpty) { + val outputPorts = connectionMap.get(p.path).get + val outputPortNames = getPortNames(outputPorts) + subscribers = subscribers :+ genPyTopicSubscription(p, portDatatype, outputPortNames) + } + else { + // In ports with no connections should still subscribe to a topic + subscribers = subscribers :+ + genPyTopicSubscription(p, portDatatype, getPortNames(IS(p.path.toISZ))) + } + } + else { + subscribers = subscribers :+ + genPyTopicSubscription(p, portDatatype, getPortNames(IS(p.path.toISZ))) + } + // Specifically for event ports, not eventdata ports (no data to be handled) + if (isEventPort(portDatatype)) { + eventPortHandlers = eventPortHandlers :+ genPyEventPortHandler(p, portDatatype) + } if (!isSporadic(component) || p.isInstanceOf[AadlDataPort]) { subscriberMethods = subscriberMethods :+ genPySubscriptionHandlerPeriodic(p, portDatatype) @@ -808,12 +1267,25 @@ object GeneratorPy { else { outMsgVars = outMsgVars :+ genPyInfrastructureOutQueue(p) outMsgVars = outMsgVars :+ genPyApplicationOutQueue(p) - if (connectionMap.get(p.path).nonEmpty) { - val inputPorts = connectionMap.get(p.path).get - val inputPortNames = getPortNames(inputPorts) - publishers = publishers :+ genPyTopicPublisher(p, portDatatype, inputPortNames) + if (invertTopicBinding) { + publishers = publishers :+ genPyTopicPublisher(p, portDatatype, getPortNames(IS(p.path.toISZ))) publisherMethods = publisherMethods :+ - genPyTopicPublishMethod(p, nodeName, inputPortNames.size) + genPyTopicPublishMethod(p, portDatatype, 1) + } + else { + if (connectionMap.get(p.path).nonEmpty) { + val inputPorts = connectionMap.get(p.path).get + val inputPortNames = getPortNames(inputPorts) + publishers = publishers :+ genPyTopicPublisher(p, portDatatype, inputPortNames) + publisherMethods = publisherMethods :+ + genPyTopicPublishMethod(p, portDatatype, inputPortNames.size) + } + else { + // Out ports with no connections should still publish to a topic + publishers = publishers :+ genPyTopicPublisher(p, portDatatype, getPortNames(IS(p.path.toISZ))) + publisherMethods = publisherMethods :+ + genPyTopicPublishMethod(p, portDatatype, 1) + } } } } @@ -837,15 +1309,15 @@ object GeneratorPy { |${(stdIncludes, "\n")} |${(typeIncludes, "\n")} | - |#================================================= - |# D O N O T E D I T T H I S F I L E - |#================================================= + |#======================================================== + |# Re-running Codegen will overwrite changes to this file + |#======================================================== | |class ${nodeName}(Node): - | def __init__(self): - | super().__init__("${component.pathAsString("_")}") + | def __init__(self): + | super().__init__("${genNodeName(component)}") | - | ${genPyCallbackGroupVar()} + | ${genPyCallbackGroupVar()} """ if (strictAADLMode) { @@ -858,28 +1330,28 @@ object GeneratorPy { if (hasInPorts) { fileBody = st"""${fileBody} - | ${subscription_options_name}.callback_group = ${callback_group_name} + | ${subscription_options_name}.callback_group = ${callback_group_name} """ } fileBody = st"""${fileBody} - | # Setting up connections - | ${(subscribers ++ publishers, "\n")}""" + | # Setting up connections + | ${(subscribers ++ publishers, "\n")}""" if(!isSporadic(component)) { if (strictAADLMode) { fileBody = st"""${fileBody} - | # timeTriggeredCaller callback timer - | ${genPyTimeTriggeredStrict(nodeName, component)} + | # timeTriggeredCaller callback timer + | ${genPyTimeTriggeredStrict(component)} """ } else { fileBody = st"""${fileBody} - | # timeTriggered callback timer - | ${genPyTimeTriggeredTimer(nodeName, component)} + | # timeTriggered callback timer + | ${genPyTimeTriggeredTimer(component)} """ } } @@ -887,20 +1359,20 @@ object GeneratorPy { if(strictAADLMode) { fileBody = st"""${fileBody} - | # Used by receiveInputs - | ${genPyInDataPortTupleVector(inTuplePortNames)}""" + | # Used by receiveInputs + | ${genPyInDataPortTupleVector(inPortNames)}""" if (!isSporadic(component)) { fileBody = st"""${fileBody} - | # Used by receiveInputs - | ${genPyInEventPortTupleVector(inTuplePortNames)}""" + | # Used by receiveInputs + | ${genPyInEventPortTupleVector(inPortNames)}""" } fileBody = st"""${fileBody} - | # Used by sendOutputs - | ${genPyOutPortTupleVector(nodeName, outPortNames)}""" + | # Used by sendOutputs + | ${genPyOutPortTupleVector(outPortNames)}""" } if (subscriberMethods.size > 0 || publisherMethods.size > 0) { @@ -914,40 +1386,42 @@ object GeneratorPy { if (inMsgVars.size > 0) { fileBody = st"""${fileBody} - | ${(inMsgVars, "\n")} + | ${(inMsgVars, "\n")} """ } if (outMsgVars.size > 0) { fileBody = st"""${fileBody} - | ${(outMsgVars, "\n")} + | ${(outMsgVars, "\n")} """ } + //TODO: Add acceptor methods + if (subscriberMethods.size > 0) { fileBody = st"""${fileBody} - |${(subscriberMethods, "\n")}""" + | ${(subscriberMethods, "\n")}""" } if (subscriptionMessageGetters.size > 0) { fileBody = st"""${fileBody} - |${(subscriptionMessageGetters, "\n")}""" + | ${(subscriptionMessageGetters, "\n")}""" } if (strictSubscriptionHandlerBaseMethods.size > 0) { fileBody = st"""${fileBody} - |${(strictSubscriptionHandlerBaseMethods, "\n")}""" + | ${(strictSubscriptionHandlerBaseMethods, "\n")}""" } if (publisherMethods.size > 0) { fileBody = st"""${fileBody} - |${(publisherMethods, "\n")} - |${(strictPutMsgMethods, "\n")}""" + | ${(publisherMethods, "\n")} + | ${(strictPutMsgMethods, "\n")}""" } } @@ -955,22 +1429,22 @@ object GeneratorPy { if (!isSporadic(component)) { fileBody = st"""${fileBody} - |${genPyTimeTriggeredCaller(nodeName)}""" + | ${genPyTimeTriggeredCaller()}""" } - val receiveInputs: ST = if (isSporadic(component)) genPyReceiveInputsSporadic(nodeName) - else genPyReceiveInputsPeriodic(nodeName) + val receiveInputs: ST = if (isSporadic(component)) genPyReceiveInputsSporadic() + else genPyReceiveInputsPeriodic() fileBody = st"""${fileBody} - |${receiveInputs} - |${genPyEnqueue(nodeName)} - |${genPySendOutputs(nodeName)}""" + | ${receiveInputs} + | ${genPyEnqueue()} + | ${genPySendOutputs()}""" } val filePath: ISZ[String] = IS("src", packageName, packageName, "base_code", fileName) - return (filePath, fileBody) + return (filePath, fileBody, true, IS()) } def genPySubscriptionHandlerSporadicStrict(inPort: AadlPort): ST = { @@ -978,10 +1452,8 @@ object GeneratorPy { // Int32 is a placeholder message value val subscriptionHandlerHeader: ST = - st"""def handle_${handlerName}(self, msg) - |{ + st"""def handle_${handlerName}(self, msg): | # Handle ${handlerName} msg - |} """ return subscriptionHandlerHeader } @@ -991,25 +1463,21 @@ object GeneratorPy { // Int32 is a placeholder message value val subscriptionHandlerHeader: ST = - st"""def handle_${handlerName}(self, msg) - |{ + st"""def handle_${handlerName}(self, msg): | # Handle ${handlerName} msg - |} """ return subscriptionHandlerHeader } def genPyTimeTriggeredMethod(): ST = { val timeTriggered: ST = - st"""def timeTriggered(self) - |{ + st"""def timeTriggered(self): | # Handle communication - |} """ return timeTriggered } - def genPyUserNodePyFile(packageName: String, component: AadlThread, strictAADLMode: B): (ISZ[String], ST) = { + def genPyUserNodePyFile(packageName: String, component: AadlThread, strictAADLMode: B): (ISZ[String], ST, B, ISZ[Marker]) = { val nodeName = component.pathAsString("_") val fileName = genPyNodeSourceName(nodeName) @@ -1040,12 +1508,10 @@ object GeneratorPy { |#================================================= |# I n i t i a l i z e E n t r y P o i n t |#================================================= - |def initialize(self) - |{ - | self.get_logger().info("Initialize Entry Point invoked"); + |def initialize(self): + | self.get_logger().info("Initialize Entry Point invoked") | | # Initialize the node - |} | |#================================================= |# C o m p u t e E n t r y P o i n t @@ -1055,7 +1521,7 @@ object GeneratorPy { val filePath: ISZ[String] = IS("src", packageName, packageName, "user_code", fileName) - return (filePath, fileBody) + return (filePath, fileBody, true, IS()) } def genPyNodeRunnerName(compNameS: String): String = { @@ -1064,7 +1530,7 @@ object GeneratorPy { return nodeNameT } - def genPyNodeRunnerFile(packageName: String, component: AadlThread): (ISZ[String], ST) = { + def genPyNodeRunnerFile(packageName: String, component: AadlThread): (ISZ[String], ST, B, ISZ[Marker]) = { val nodeName = component.pathAsString("_") val fileName = genPyNodeRunnerName(nodeName) @@ -1078,13 +1544,13 @@ object GeneratorPy { |#================================================= | |class ${nodeName}(${nodeName}_base): - | def __init__(self): - | # invoke initialize entry point - | super().__init__() + | def __init__(self): + | # invoke initialize entry point + | super().__init__() | - | ${nodeName}() + | ${nodeName}() | - | self.get_logger().info("${nodeName} infrastructure set up") + | self.get_logger().info("${nodeName} infrastructure set up") | |def main(args=None): | rclpy.init(args=args) @@ -1100,16 +1566,17 @@ object GeneratorPy { val filePath: ISZ[String] = IS("src", packageName, packageName, "base_code", fileName) - return (filePath, fileBody) + return (filePath, fileBody, true, IS()) } def genPyNodeFiles(modelName: String, threadComponents: ISZ[AadlThread], connectionMap: Map[ISZ[String], ISZ[ISZ[String]]], - datatypeMap: Map[AadlType, (String, ISZ[String])], strictAADLMode: B, reporter: Reporter): ISZ[(ISZ[String], ST)] = { + datatypeMap: Map[AadlType, (String, ISZ[String])], hasConverterFiles: B, strictAADLMode: B, + invertTopicBinding: B, reporter: Reporter): ISZ[(ISZ[String], ST, B, ISZ[Marker])] = { val top_level_package_nameT: String = genPyPackageName(modelName) - var py_files: ISZ[(ISZ[String], ST)] = IS() + var py_files: ISZ[(ISZ[String], ST, B, ISZ[Marker])] = IS() for (comp <- threadComponents) { py_files = - py_files :+ genPyBaseNodePyFile(top_level_package_nameT, comp, connectionMap, datatypeMap, strictAADLMode, reporter) + py_files :+ genPyBaseNodePyFile(top_level_package_nameT, comp, connectionMap, datatypeMap, strictAADLMode, invertTopicBinding, reporter) py_files = py_files :+ genPyUserNodePyFile(top_level_package_nameT, comp, strictAADLMode) py_files = @@ -1118,18 +1585,123 @@ object GeneratorPy { return py_files } + def genPyEnumConverters(packageName: String, enumTypes: ISZ[(String, AadlType)], strictAADLMode: B): ISZ[ST] = { + var converters: ISZ[ST] = IS() + + //TODO: Refactor to Python + + for (enum <- enumTypes) { + val enumName: String = ops.StringOps(enum._2.classifier.apply(enum._2.classifier.size - 1)).replaceAllLiterally("_", "") + val enumValues: ISZ[String] = enum._2.asInstanceOf[EnumType].values + + var cases: ISZ[ST] = IS() + + for (value <- enumValues) { + cases = cases :+ + st"""case ${packageName}_interfaces::msg::${enumName}::${StringOps(enum._1).toUpper}_${StringOps(value).toUpper}: + | return "${enumName} ${value}";""" + } + + if (strictAADLMode) { + converters = converters :+ + st"""const char* enumToString(${packageName}_interfaces::msg::${enumName} value) { + | switch (value.${enum._1}) { + | ${(cases, "\n")} + | default: + | return "Unknown value for ${enumName}"; + | } + |} + """ + } + else { + converters = converters :+ + st"""const char* enumToString(${packageName}_interfaces::msg::${enumName}* value) { + | switch (value->${enum._1}) { + | ${(cases, "\n")} + | default: + | return "Unknown value for ${enumName}"; + | } + |} + """ + } + } + + return converters + } + + def genPyEnumConverterFile(packageName: String, enumTypes: ISZ[(String, AadlType)], + strictAADLMode: B): (ISZ[String], ST, B, ISZ[Marker]) = { + //TODO: add enum header stuff + val fileBody = + st"""#======================================================== + |# Re-running Codegen will overwrite changes to this file + |#======================================================== + | + |${(genPyEnumConverters(packageName, enumTypes, strictAADLMode), "\n")} + """ + + val filePath: ISZ[String] = IS("src", packageName, "src", "base_code", "enum_converter.py") + + return (filePath, fileBody, T, IS()) + } + + def genPyEnumConverterFiles(modelName: String, datatypeMap: Map[AadlType, (String, ISZ[String])], + strictAADLMode: B): ISZ[(ISZ[String], ST, B, ISZ[Marker])] = { + var enumTypes: ISZ[(String, AadlType)] = IS() + + for (key <- datatypeMap.keys) { + key match { + case _: EnumType => + val datatype: String = datatypeMap.get(key).get._2.apply(0) + val datatypeName: String = StringOps(datatype).substring(StringOps(datatype).indexOf(' ') + 1, datatype.size) + enumTypes = enumTypes :+ (datatypeName, key) + case x => + } + } + + if (enumTypes.size == 0) { + return IS() + } + + var files: ISZ[(ISZ[String], ST, B, ISZ[Marker])] = IS() + val packageName: String = genPyPackageName(modelName) + + files = files :+ genPyEnumConverterFile(packageName, enumTypes, strictAADLMode) + + return files + } + //================================================ // P a c k a g e G e n e r a t o r s //================================================ // TODO: Python pkgs def genPyNodePkg(modelName: String, threadComponents: ISZ[AadlThread], connectionMap: Map[ISZ[String], ISZ[ISZ[String]]], - datatypeMap: Map[AadlType, (String, ISZ[String])], strictAADLMode: B, reporter: Reporter): ISZ[(ISZ[String], ST)] = { - var files: ISZ[(ISZ[String], ST)] = IS() + datatypeMap: Map[AadlType, (String, ISZ[String])], strictAADLMode: B, invertTopicBinding: B, + reporter: Reporter): ISZ[(ISZ[String], ST, B, ISZ[Marker])] = { + var files: ISZ[(ISZ[String], ST, B, ISZ[Marker])] = IS() + + val converterFiles: ISZ[(ISZ[String], ST, B, ISZ[Marker])] = genPyEnumConverterFiles(modelName, datatypeMap, strictAADLMode) + val hasConverterFiles: B = (converterFiles.size > 0) + val top_level_package_nameT: String = genPyPackageName(modelName) //files = files :+ genPyFormatLaunchFile(modelName, threadComponents) + files = files ++ genPyNodeFiles(modelName, threadComponents, connectionMap, datatypeMap, hasConverterFiles, strictAADLMode, + invertTopicBinding, reporter) + +// files = files :+ genPyFormatLaunchFile(modelName,ad) +// files = files :+ genCMakeLaunchFile(modelName) +// files = files :+ genXmlLaunchFile(modelName) + files = files :+ genPyInitFile(top_level_package_nameT) + files = files :+ genPySubInitFile(modelName, "base_code") + files = files :+ genPySubInitFile(modelName, "user_code") + files = files :+ genPyResourceFile(modelName) files = files :+ genPySetupFile(modelName, threadComponents) - files = files ++ genPyNodeFiles(modelName, threadComponents, connectionMap, datatypeMap, strictAADLMode, reporter) + files = files :+ genXmlPackageFile(modelName) + files = files :+ genCfgSetupFile(modelName) + files = files :+ genPyCopyrightFile(modelName) + files = files :+ genPyFlakeFile(modelName) + files = files :+ genPyPrepFile(modelName) return files } @@ -1145,4 +1717,16 @@ object GeneratorPy { return files } + + // The same datatype package will work regardless of other packages' types + // ROS2 data/message types are defined in a "{package_name}_interfaces" package according to convention + def genInterfacesPkg(modelName: String, datatypeMap: Map[AadlType, (String, ISZ[String])]): ISZ[(ISZ[String], ST, B, ISZ[Marker])] = { + var files: ISZ[(ISZ[String], ST, B, ISZ[Marker])] = IS() + + files = files ++ genMsgFiles(modelName, datatypeMap) + files = files :+ genInterfacesCMakeListsFile(modelName, datatypeMap) + files = files :+ genInterfacesPackageFile(modelName) + + return files + } } diff --git a/shared/src/main/scala/org/sireum/hamr/codegen/ros2/Ros2Codegen.scala b/shared/src/main/scala/org/sireum/hamr/codegen/ros2/Ros2Codegen.scala index ca18665a..e36a0ead 100644 --- a/shared/src/main/scala/org/sireum/hamr/codegen/ros2/Ros2Codegen.scala +++ b/shared/src/main/scala/org/sireum/hamr/codegen/ros2/Ros2Codegen.scala @@ -49,7 +49,8 @@ object Ros2Codegen { case "Cpp" => files = Generator.genCppNodePkg(modelName, threadComponents, connectionMap, datatypeMap, options.strictAadlMode, options.invertTopicBinding, reporter) - //case "Python" => files = Generator.genPyNodePkg(modelName, threadComponents, connectionMap, options.strictAadlMode) + case "Python" => files = GeneratorPy.genPyNodePkg(modelName, threadComponents, connectionMap, datatypeMap, options.strictAadlMode, + options.invertTopicBinding, reporter) case _ => reporter.error(None(), toolName, s"Unknown code type: ${options.ros2NodesLanguage.name}") } @@ -59,7 +60,12 @@ object Ros2Codegen { case _ => reporter.error(None(), toolName, s"Unknown code type: ${options.ros2NodesLanguage.name}") } - files = files ++ Generator.genInterfacesPkg(modelName, datatypeMap) + //files = files ++ Generator.genInterfacesPkg(modelName, datatypeMap) + options.ros2NodesLanguage.name match { + case "Cpp" => files = files ++ Generator.genInterfacesPkg(modelName, datatypeMap) + case "Python" => files = files ++ GeneratorPy.genInterfacesPkg(modelName, datatypeMap) + case _ => reporter.error(None(), toolName, s"Unknown code type: ${options.ros2NodesLanguage.name}") + } for (file <- files) { var filePath: String = "" From df10c3c8c38bf3581e3189fa4d4e7b7a1ecae8de Mon Sep 17 00:00:00 2001 From: Cat Date: Thu, 6 Mar 2025 13:09:28 -0600 Subject: [PATCH 06/18] Refactored user and runner Python files --- jvm/src/test | 2 +- .../hamr/codegen/ros2/GeneratorPy.scala | 258 +++++++++--------- 2 files changed, 135 insertions(+), 125 deletions(-) diff --git a/jvm/src/test b/jvm/src/test index b4d7d40c..6d94f05e 160000 --- a/jvm/src/test +++ b/jvm/src/test @@ -1 +1 @@ -Subproject commit b4d7d40c07724bd222576f0c362f6c40b53e64b3 +Subproject commit 6d94f05e00304ad467b84f81c283f70854fb3bc4 diff --git a/shared/src/main/scala/org/sireum/hamr/codegen/ros2/GeneratorPy.scala b/shared/src/main/scala/org/sireum/hamr/codegen/ros2/GeneratorPy.scala index e62e77d0..ccd2dd8b 100644 --- a/shared/src/main/scala/org/sireum/hamr/codegen/ros2/GeneratorPy.scala +++ b/shared/src/main/scala/org/sireum/hamr/codegen/ros2/GeneratorPy.scala @@ -22,12 +22,8 @@ object GeneratorPy { val py_src_node_entry_point_name: String = "main" val py_node_runner_name_suffix: String = "_runner.py" - // TODO: Reentrant or mutually exclusive (or single-threaded executor)? - // This value will work for Python and C++ code - val callback_group_type: String = "Reentrant" - // TODO: Confirm this name (and maybe remove based on how callback groups are done) + val callback_group_type: String = "ReentrantCallbackGroup" val callback_group_name: String = "cb_group_" - val subscription_options_name: String = "subscription_options_" def genPyLaunchFileName(compNameS: String): String = { // create launch file name @@ -58,7 +54,7 @@ object GeneratorPy { } def isEventPort(portType: String): B = { - return ops.StringOps(portType).substring(portType.size - 7, portType.size) == "::Empty" + return ops.StringOps(portType).substring(0, portType.size) == "Empty" } def genNodeName(component: AadlThread): String = { @@ -232,7 +228,7 @@ object GeneratorPy { val top_level_package_nameT: String = genPyPackageName(modelName) val fileName: String = "package.xml" - val packages: ISZ[String] = IS("example_interfaces") + val packages: ISZ[String] = IS(s"${genPyPackageName(modelName)}_interfaces") val pkgDependencies: ISZ[ST] = genPackageFilePkgDependencies(packages) val setupFileBody = @@ -676,7 +672,7 @@ object GeneratorPy { | "${topicName}", | self.${handler}, | 1, - | callback_group=self.${subscription_options_name}); + | callback_group=self.${callback_group_name}) """ return portCode } @@ -692,7 +688,7 @@ object GeneratorPy { | "${outPortName}", | self.${handler}, | 1, - | callback_group=self.${subscription_options_name}); + | callback_group=self.${callback_group_name}) """ counter = counter + 1 } @@ -744,7 +740,7 @@ object GeneratorPy { else { handlerCode = st"""void handle_${handlerName}_base(self, msg): - | if isInstance(msg, ${portType}): + | if type(msg) is ${portType}: | typedMsg = ${portType}() | typedMsg.data = msg | handle_${handlerName}(typedMsg) @@ -816,12 +812,12 @@ object GeneratorPy { // Int32 is a placeholder message value val publisherCode: ST = st"""def sendOut_${handlerName}(self, msg): - | if isinstance(msg, Int32): + | if type(msg) is ${portType}: | typedMsg = ${portType}() | typedMsg.data = msg | ${(publishers, "\n")} | else: - | this.get_logger().error("Sending out wrong type of variable on port ${handlerName}.\nThis shouldn't be possible. If you are seeing this message, please notify this tool's current maintainer.") + | self.get_logger().error("Sending out wrong type of variable on port ${handlerName}.\nThis shouldn't be possible. If you are seeing this message, please notify this tool's current maintainer.") |""" return publisherCode } @@ -834,7 +830,7 @@ object GeneratorPy { if (isEventPort(portType)) { putMsgCode = st"""def put_${handlerName}(self, msg): - | self.enqueue(applicationOut_${handlerName}, ${portType}()) + | self.enqueue(self.applicationOut_${handlerName}, ${portType}()) |""" } else { @@ -842,7 +838,7 @@ object GeneratorPy { st"""def put_${handlerName}(self, msg): | typedMsg = ${portType}() | typedMsg.data = msg - | self.enqueue(applicationOut_${handlerName}, typedMsg) + | self.enqueue(self.applicationOut_${handlerName}, typedMsg) |""" } return putMsgCode @@ -875,7 +871,7 @@ object GeneratorPy { | "${topicName}", | ${handler}, | 1, - | callback_group=self.${subscription_options_name}) + | callback_group=self.${callback_group_name}) |""" return portCode } @@ -891,7 +887,7 @@ object GeneratorPy { | "${outPortName}", | ${handler}, | 1, - | callback_group=self.${subscription_options_name}) + | callback_group=self.${callback_group_name}) |""" counter = counter + 1 } @@ -1010,7 +1006,7 @@ object GeneratorPy { def genPyCallbackGroupVar(): ST = { val callbackGroup: ST = - st"${callback_group_name} = this.create_callback_group(${callback_group_type})" + st"self.${callback_group_name} = ${callback_group_type}()" return callbackGroup } @@ -1038,7 +1034,7 @@ object GeneratorPy { } val vector: ST = - st"""inDataPortTupleVector = [ + st"""self.inDataPortTupleVector = [ | ${(tuples, ",\n")} | ] """ @@ -1053,7 +1049,7 @@ object GeneratorPy { } val vector: ST = - st"""inEventPortTupleVector = [ + st"""self.inEventPortTupleVector = [ | ${(tuples, ",\n")} | ] """ @@ -1068,7 +1064,7 @@ object GeneratorPy { } val vector: ST = - st"""outPortTupleVector = [ + st"""self.outPortTupleVector = [ | ${(tuples, ",\n")} | ] """ @@ -1088,16 +1084,16 @@ object GeneratorPy { def genPyReceiveInputsSporadic(): ST = { val method: ST = st"""def receiveInputs(self, infrastructureQueue, applicationQueue): - | if !infrastructureQueue.empty(): - | eventMsg = infrastructureQueue.front() - | infrastructureQueue.pop() - | self.enqueue(applicationQueue, eventMsg) + | if not(infrastructureQueue.empty()): + | eventMsg = infrastructureQueue.front() + | infrastructureQueue.pop() + | self.enqueue(applicationQueue, eventMsg) | - | for port in inDataPortTupleVector: - | infrastructureQueue = port[0] - | if !infrastructureQueue.empty(): - | msg = infrastructureQueue.front() - | self.enqueue(port[1], msg) + | for port in self.inDataPortTupleVector: + | infrastructureQueue = port[0] + | if not(infrastructureQueue.empty()): + | msg = infrastructureQueue.front() + | self.enqueue(port[1], msg) """ return method } @@ -1105,18 +1101,18 @@ object GeneratorPy { def genPyReceiveInputsPeriodic(): ST = { val method: ST = st"""def receiveInputs(self): - | for port in inDataPortTupleVector: - | infrastructureQueue = port[0] - | if !infrastructureQueue.empty(): - | msg = infrastructureQueue.front() - | self.enqueue(*port[1], msg) + | for port in self.inDataPortTupleVector: + | infrastructureQueue = port[0] + | if not(infrastructureQueue.empty()): + | msg = infrastructureQueue.front() + | self.enqueue(*port[1], msg) | - | for port in inEventPortTupleVector: - | infrastructureQueue = port[0] - | if !infrastructureQueue.empty(): - | msg = infrastructureQueue.front() - | infrastructureQueue.pop() - | self.enqueue(port[1], msg) + | for port in self.inEventPortTupleVector: + | infrastructureQueue = port[0] + | if not(infrastructureQueue.empty()): + | msg = infrastructureQueue.front() + | infrastructureQueue.pop() + | self.enqueue(port[1], msg) """ return method } @@ -1134,19 +1130,19 @@ object GeneratorPy { def genPySendOutputs(): ST = { val method: ST = st"""def sendOutputs(self): - | for port in outPortTupleVector: - | applicationQueue = port[0] - | if applicationQueue.size() != 0: - | msg = applicationQueue.front() - | applicationQueue.pop() - | enqueue(port[1], msg) + | for port in self.outPortTupleVector: + | applicationQueue = port[0] + | if applicationQueue.size() != 0: + | msg = applicationQueue.front() + | applicationQueue.pop() + | self.enqueue(port[1], msg) | - | for port in outPortTupleVector: - | infrastructureQueue = port[1] - | if infrastructureQueue.size() != 0: - | msg = infrastructureQueue.front() - | infrastructureQueue.pop() - | (sport[2])(msg) + | for port in self.outPortTupleVector: + | infrastructureQueue = port[1] + | if infrastructureQueue.size() != 0: + | msg = infrastructureQueue.front() + | infrastructureQueue.pop() + | (port[2])(msg) """ return method } @@ -1307,6 +1303,7 @@ object GeneratorPy { |import rclpy |from rclpy.node import Node |${(stdIncludes, "\n")} + |from rclpy.callback_groups import ${callback_group_type} |${(typeIncludes, "\n")} | |#======================================================== @@ -1323,35 +1320,28 @@ object GeneratorPy { if (strictAADLMode) { fileBody = st"""${fileBody} - | MsgType = Union[${(msgTypes, ", ")}] + | MsgType = Union[${(msgTypes, ", ")}] """ } - if (hasInPorts) { - fileBody = - st"""${fileBody} - | ${subscription_options_name}.callback_group = ${callback_group_name} - """ - } - fileBody = st"""${fileBody} - | # Setting up connections - | ${(subscribers ++ publishers, "\n")}""" + | # Setting up connections + | ${(subscribers ++ publishers, "\n")}""" if(!isSporadic(component)) { if (strictAADLMode) { fileBody = st"""${fileBody} - | # timeTriggeredCaller callback timer - | ${genPyTimeTriggeredStrict(component)} + | # timeTriggeredCaller callback timer + | ${genPyTimeTriggeredStrict(component)} """ } else { fileBody = st"""${fileBody} - | # timeTriggered callback timer - | ${genPyTimeTriggeredTimer(component)} + | # timeTriggered callback timer + | ${genPyTimeTriggeredTimer(component)} """ } } @@ -1359,20 +1349,20 @@ object GeneratorPy { if(strictAADLMode) { fileBody = st"""${fileBody} - | # Used by receiveInputs - | ${genPyInDataPortTupleVector(inPortNames)}""" + | # Used by receiveInputs + | ${genPyInDataPortTupleVector(inPortNames)}""" if (!isSporadic(component)) { fileBody = st"""${fileBody} - | # Used by receiveInputs - | ${genPyInEventPortTupleVector(inPortNames)}""" + | # Used by receiveInputs + | ${genPyInEventPortTupleVector(inPortNames)}""" } fileBody = st"""${fileBody} - | # Used by sendOutputs - | ${genPyOutPortTupleVector(outPortNames)}""" + | # Used by sendOutputs + | ${genPyOutPortTupleVector(outPortNames)}""" } if (subscriberMethods.size > 0 || publisherMethods.size > 0) { @@ -1386,14 +1376,14 @@ object GeneratorPy { if (inMsgVars.size > 0) { fileBody = st"""${fileBody} - | ${(inMsgVars, "\n")} + | ${(inMsgVars, "\n")} """ } if (outMsgVars.size > 0) { fileBody = st"""${fileBody} - | ${(outMsgVars, "\n")} + | ${(outMsgVars, "\n")} """ } @@ -1402,26 +1392,26 @@ object GeneratorPy { if (subscriberMethods.size > 0) { fileBody = st"""${fileBody} - | ${(subscriberMethods, "\n")}""" + | ${(subscriberMethods, "\n")}""" } if (subscriptionMessageGetters.size > 0) { fileBody = st"""${fileBody} - | ${(subscriptionMessageGetters, "\n")}""" + | ${(subscriptionMessageGetters, "\n")}""" } if (strictSubscriptionHandlerBaseMethods.size > 0) { fileBody = st"""${fileBody} - | ${(strictSubscriptionHandlerBaseMethods, "\n")}""" + | ${(strictSubscriptionHandlerBaseMethods, "\n")}""" } if (publisherMethods.size > 0) { fileBody = st"""${fileBody} - | ${(publisherMethods, "\n")} - | ${(strictPutMsgMethods, "\n")}""" + | ${(publisherMethods, "\n")} + | ${(strictPutMsgMethods, "\n")}""" } } @@ -1444,55 +1434,72 @@ object GeneratorPy { val filePath: ISZ[String] = IS("src", packageName, packageName, "base_code", fileName) - return (filePath, fileBody, true, IS()) + return (filePath, fileBody, T, IS()) } - def genPySubscriptionHandlerSporadicStrict(inPort: AadlPort): ST = { + def genPySubscriptionHandlerSporadicStrict(inPort: AadlPort, portType: String): ST = { val handlerName = inPort.identifier - // Int32 is a placeholder message value - val subscriptionHandlerHeader: ST = - st"""def handle_${handlerName}(self, msg): - | # Handle ${handlerName} msg + var subscriptionHandlerHeader: ST = st"" + if (isEventPort(portType)) { + subscriptionHandlerHeader = + st"""def handle_${handlerName}(self): + | pass # Handle ${handlerName} event + """ + } + else { + subscriptionHandlerHeader = + st"""def handle_${handlerName}(self, msg): + | pass # Handle ${handlerName} msg """ + } return subscriptionHandlerHeader } - def genPySubscriptionHandlerSporadic(inPort: AadlPort): ST = { + def genPySubscriptionHandlerSporadic(inPort: AadlPort, portType: String): ST = { val handlerName = inPort.identifier - // Int32 is a placeholder message value - val subscriptionHandlerHeader: ST = - st"""def handle_${handlerName}(self, msg): - | # Handle ${handlerName} msg + var subscriptionHandlerHeader: ST = st"" + if (isEventPort(portType)) { + subscriptionHandlerHeader = + st"""def handle_${handlerName}(): + | pass # Handle ${handlerName} event """ + } + else { + subscriptionHandlerHeader = + st"""def handle_${handlerName}(msg): + | pass # Handle ${handlerName} msg + """ + } return subscriptionHandlerHeader } def genPyTimeTriggeredMethod(): ST = { val timeTriggered: ST = - st"""def timeTriggered(self): - | # Handle communication + st"""def timeTriggered(): + | pass # Handle communication """ return timeTriggered } - def genPyUserNodePyFile(packageName: String, component: AadlThread, strictAADLMode: B): (ISZ[String], ST, B, ISZ[Marker]) = { - val nodeName = component.pathAsString("_") + def genPyUserNodePyFile(packageName: String, component: AadlThread, datatypeMap: Map[AadlType, (String, ISZ[String])], + hasConverterFiles: B, strictAADLMode: B, reporter: Reporter): (ISZ[String], ST, B, ISZ[Marker]) = { + val nodeName = genNodeName(component) val fileName = genPyNodeSourceName(nodeName) var subscriptionHandlers: ISZ[ST] = IS() if (isSporadic(component)) { for (p <- component.getPorts()) { - // TODO: Datatypes + val portDatatype: String = genPortDatatype(p, packageName, datatypeMap, reporter) if (p.direction == Direction.In && !p.isInstanceOf[AadlDataPort]) { if (strictAADLMode) { subscriptionHandlers = subscriptionHandlers :+ - genPySubscriptionHandlerSporadicStrict(p) + genPySubscriptionHandlerSporadicStrict(p, portDatatype) } else { subscriptionHandlers = subscriptionHandlers :+ - genPySubscriptionHandlerSporadic(p) + genPySubscriptionHandlerSporadic(p, portDatatype) } } } @@ -1501,15 +1508,24 @@ object GeneratorPy { subscriptionHandlers = subscriptionHandlers :+ genPyTimeTriggeredMethod() } + //TODO: converter files + val fileBody = st"""#!/usr/bin/env python3 |import rclpy |from rclpy.node import Node + |from ${packageName}.base_code.${nodeName}_runner import ${nodeName} + |#=========================================================== + |# This file will not be overwritten when re-running Codegen + |#=========================================================== + | |#================================================= |# I n i t i a l i z e E n t r y P o i n t |#================================================= - |def initialize(self): - | self.get_logger().info("Initialize Entry Point invoked") + |def initialize(n): + | node = ${nodeName}() + | node.data = n + | node.get_logger().info("Initialize Entry Point invoked") | | # Initialize the node | @@ -1521,7 +1537,7 @@ object GeneratorPy { val filePath: ISZ[String] = IS("src", packageName, packageName, "user_code", fileName) - return (filePath, fileBody, true, IS()) + return (filePath, fileBody, F, IS()) } def genPyNodeRunnerName(compNameS: String): String = { @@ -1531,42 +1547,43 @@ object GeneratorPy { } def genPyNodeRunnerFile(packageName: String, component: AadlThread): (ISZ[String], ST, B, ISZ[Marker]) = { - val nodeName = component.pathAsString("_") + val nodeName = genNodeName(component) val fileName = genPyNodeRunnerName(nodeName) val fileBody = st"""#!/usr/bin/env python3 |import rclpy |from rclpy.node import Node - |from ${packageName}.user_code.${nodeName}_src import ${nodeName} - |#================================================= - |# D O N O T E D I T T H I S F I L E - |#================================================= + |from rclpy.executors import MultiThreadedExecutor + |from ${packageName}.user_code.${nodeName}_src import * + |from ${packageName}.user_code.${nodeName}_base_src import ${nodeName}_base + |#======================================================== + |# Re-running Codegen will overwrite changes to this file + |#======================================================== | |class ${nodeName}(${nodeName}_base): | def __init__(self): - | # invoke initialize entry point | super().__init__() - | - | ${nodeName}() + | # invoke initialize entry point + | initialize() | | self.get_logger().info("${nodeName} infrastructure set up") | |def main(args=None): - | rclpy.init(args=args) - | node = ${nodeName}() - | executor = MultiThreadedExecutor() - | executor.add_node(node) - | executor.spin() - | rclpy.shutdown() + | rclpy.init(args=args) + | node = ${nodeName}() + | executor = MultiThreadedExecutor() + | executor.add_node(node) + | executor.spin() + | rclpy.shutdown() | |if __name__ == "__main__": - | main() + | main() """ val filePath: ISZ[String] = IS("src", packageName, packageName, "base_code", fileName) - return (filePath, fileBody, true, IS()) + return (filePath, fileBody, T, IS()) } def genPyNodeFiles(modelName: String, threadComponents: ISZ[AadlThread], connectionMap: Map[ISZ[String], ISZ[ISZ[String]]], @@ -1578,7 +1595,7 @@ object GeneratorPy { py_files = py_files :+ genPyBaseNodePyFile(top_level_package_nameT, comp, connectionMap, datatypeMap, strictAADLMode, invertTopicBinding, reporter) py_files = - py_files :+ genPyUserNodePyFile(top_level_package_nameT, comp, strictAADLMode) + py_files :+ genPyUserNodePyFile(top_level_package_nameT, comp, datatypeMap, hasConverterFiles, strictAADLMode, reporter) py_files = py_files :+ genPyNodeRunnerFile(top_level_package_nameT, comp) } @@ -1631,7 +1648,6 @@ object GeneratorPy { def genPyEnumConverterFile(packageName: String, enumTypes: ISZ[(String, AadlType)], strictAADLMode: B): (ISZ[String], ST, B, ISZ[Marker]) = { - //TODO: add enum header stuff val fileBody = st"""#======================================================== |# Re-running Codegen will overwrite changes to this file @@ -1674,8 +1690,6 @@ object GeneratorPy { //================================================ // P a c k a g e G e n e r a t o r s //================================================ - - // TODO: Python pkgs def genPyNodePkg(modelName: String, threadComponents: ISZ[AadlThread], connectionMap: Map[ISZ[String], ISZ[ISZ[String]]], datatypeMap: Map[AadlType, (String, ISZ[String])], strictAADLMode: B, invertTopicBinding: B, reporter: Reporter): ISZ[(ISZ[String], ST, B, ISZ[Marker])] = { @@ -1685,13 +1699,9 @@ object GeneratorPy { val hasConverterFiles: B = (converterFiles.size > 0) val top_level_package_nameT: String = genPyPackageName(modelName) - //files = files :+ genPyFormatLaunchFile(modelName, threadComponents) files = files ++ genPyNodeFiles(modelName, threadComponents, connectionMap, datatypeMap, hasConverterFiles, strictAADLMode, invertTopicBinding, reporter) -// files = files :+ genPyFormatLaunchFile(modelName,ad) -// files = files :+ genCMakeLaunchFile(modelName) -// files = files :+ genXmlLaunchFile(modelName) files = files :+ genPyInitFile(top_level_package_nameT) files = files :+ genPySubInitFile(modelName, "base_code") files = files :+ genPySubInitFile(modelName, "user_code") From 7062fa7bb53e7937b7c78597250328de27697c6d Mon Sep 17 00:00:00 2001 From: Cat Date: Thu, 6 Mar 2025 16:00:53 -0600 Subject: [PATCH 07/18] Added acceptor methods --- .../hamr/codegen/ros2/GeneratorPy.scala | 105 +++++++++++++----- 1 file changed, 77 insertions(+), 28 deletions(-) diff --git a/shared/src/main/scala/org/sireum/hamr/codegen/ros2/GeneratorPy.scala b/shared/src/main/scala/org/sireum/hamr/codegen/ros2/GeneratorPy.scala index ccd2dd8b..d59e544d 100644 --- a/shared/src/main/scala/org/sireum/hamr/codegen/ros2/GeneratorPy.scala +++ b/shared/src/main/scala/org/sireum/hamr/codegen/ros2/GeneratorPy.scala @@ -361,8 +361,8 @@ object GeneratorPy { |@pytest.mark.linter |def test_flake8(): | rc, errors = main_with_errors(argv=[]) - | assert rc == 0, \ - | 'Found %d code style errors / warnings:\n' % len(errors) + \ + | assert rc == 0, \\ + | 'Found %d code style errors / warnings:\n' % len(errors) + \\ | '\n'.join(errors) """ @@ -670,7 +670,7 @@ object GeneratorPy { st"""self.${portName}_subscription_ = self.create_subscription( | ${portType}, | "${topicName}", - | self.${handler}, + | ${handler}, | 1, | callback_group=self.${callback_group_name}) """ @@ -686,7 +686,7 @@ object GeneratorPy { st"""self.${portName}_subscription_${counter} = self.create_subscription( | ${portType}, | "${outPortName}", - | self.${handler}, + | ${handler}, | 1, | callback_group=self.${callback_group_name}) """ @@ -699,6 +699,41 @@ object GeneratorPy { return fanPortCode } + def genPyThread(inPort: AadlPort, portType: String): ST = { + val handlerName = inPort.identifier + + val thread: ST = + st"""def ${portType}_thread(self): + | with self.lock_: + | self.receiveInputs(infrastructureIn_${handlerName}, applicationIn_${handlerName}) + | if (applicationIn_${handlerName}.empty()) return + | self.handle_${handlerName}_base(applicationIn_${handlerName}.front()) + | self.applicationIn_${handlerName}.pop() + | self.sendOutputs() + """ + + return thread + } + + def genPyMessageAcceptor(inPort: AadlPort, isSporadic: B, portType: String): ST = { + val handlerName = inPort.identifier + + val handler: ST = + if (!isSporadic || inPort.isInstanceOf[AadlDataPort]) st"self.enqueue(infrastructureIn_${handlerName}, msg)" + else + st"""self.enqueue(infrastructureIn_${handlerName}, msg); + | thread = threading.Thread(target=${portType}_thread) + | thread.daemon = True + | thread.start() + """ + + return st"""def accept_${handlerName}(self, ${portType} msg): + | typedMsg = ${portType}() + | typedMsg.data = msg + | ${handler} + |""" + } + def genPyInfrastructureInQueue(inPort: AadlPort): ST = { val portName = inPort.identifier @@ -995,7 +1030,7 @@ object GeneratorPy { } else { publisherCode = - st"""def put_${handlerName}(self, msg) + st"""def put_${handlerName}(self, msg): | typedMsg = ${portType}() | typedMsg.data = msg | ${(publishers, "\n")} @@ -1075,7 +1110,7 @@ object GeneratorPy { val timeTriggered: ST = st"""def timeTriggeredCaller(self): | self.receiveInputs() - | timeTriggered() + | self.timeTriggered() | self.sendOutputs() """ return timeTriggered @@ -1192,7 +1227,14 @@ object GeneratorPy { subscribers = subscribers :+ genPyTopicSubscriptionStrict(p, portDatatype, getPortNames(IS(p.path.toISZ))) } - //TODO: MessageAcceptors + if (isSporadic(component) && !p.isInstanceOf[AadlDataPort]) { + strictSubscriptionMessageAcceptorMethods = strictSubscriptionMessageAcceptorMethods :+ + genPyThread(p, portDatatype) + } + + strictSubscriptionMessageAcceptorMethods = strictSubscriptionMessageAcceptorMethods :+ + genPyMessageAcceptor(p, isSporadic(component), portDatatype) + inMsgVars = inMsgVars :+ genPyInfrastructureInQueue(p) inMsgVars = inMsgVars :+ genPyApplicationInQueue(p) @@ -1321,6 +1363,7 @@ object GeneratorPy { fileBody = st"""${fileBody} | MsgType = Union[${(msgTypes, ", ")}] + | self.lock_ = threading.Lock() """ } @@ -1376,18 +1419,22 @@ object GeneratorPy { if (inMsgVars.size > 0) { fileBody = st"""${fileBody} - | ${(inMsgVars, "\n")} + | ${(inMsgVars, "\n")} """ } if (outMsgVars.size > 0) { fileBody = st"""${fileBody} - | ${(outMsgVars, "\n")} + | ${(outMsgVars, "\n")} """ } - //TODO: Add acceptor methods + if (strictSubscriptionMessageAcceptorMethods.size > 0) { + fileBody = + st"""${fileBody} + |${(strictSubscriptionMessageAcceptorMethods, "\n")}""" + } if (subscriberMethods.size > 0) { fileBody = @@ -1605,8 +1652,6 @@ object GeneratorPy { def genPyEnumConverters(packageName: String, enumTypes: ISZ[(String, AadlType)], strictAADLMode: B): ISZ[ST] = { var converters: ISZ[ST] = IS() - //TODO: Refactor to Python - for (enum <- enumTypes) { val enumName: String = ops.StringOps(enum._2.classifier.apply(enum._2.classifier.size - 1)).replaceAllLiterally("_", "") val enumValues: ISZ[String] = enum._2.asInstanceOf[EnumType].values @@ -1615,30 +1660,31 @@ object GeneratorPy { for (value <- enumValues) { cases = cases :+ - st"""case ${packageName}_interfaces::msg::${enumName}::${StringOps(enum._1).toUpper}_${StringOps(value).toUpper}: - | return "${enumName} ${value}";""" + st"""case ${enumName}.${StringOps(enum._1).toUpper}_${StringOps(value).toUpper}: + | return "${enumName} ${value}"""" } + //Only difference was a pointer? if (strictAADLMode) { converters = converters :+ - st"""const char* enumToString(${packageName}_interfaces::msg::${enumName} value) { - | switch (value.${enum._1}) { + st"""def enumToString(value): + | typedValue = ${enumName}() + | typedValue.data = value + | match (typedValue.${enum._1}) { | ${(cases, "\n")} - | default: - | return "Unknown value for ${enumName}"; - | } - |} + | case default: + | return "Unknown value for ${enumName}" """ } else { converters = converters :+ - st"""const char* enumToString(${packageName}_interfaces::msg::${enumName}* value) { - | switch (value->${enum._1}) { + st"""def enumToString(value): + | typedValue = ${enumName}() + | typedValue.data = value + | match (typedValue.${enum._1}): | ${(cases, "\n")} - | default: - | return "Unknown value for ${enumName}"; - | } - |} + | case default: + | return "Unknown value for ${enumName}" """ } } @@ -1649,14 +1695,16 @@ object GeneratorPy { def genPyEnumConverterFile(packageName: String, enumTypes: ISZ[(String, AadlType)], strictAADLMode: B): (ISZ[String], ST, B, ISZ[Marker]) = { val fileBody = - st"""#======================================================== + st"""from datatypes_system_py_pkg_interfaces.msg import MyEnum + | + |#======================================================== |# Re-running Codegen will overwrite changes to this file |#======================================================== | |${(genPyEnumConverters(packageName, enumTypes, strictAADLMode), "\n")} """ - val filePath: ISZ[String] = IS("src", packageName, "src", "base_code", "enum_converter.py") + val filePath: ISZ[String] = IS("src", packageName, packageName, "base_code", "enum_converter.py") return (filePath, fileBody, T, IS()) } @@ -1699,6 +1747,7 @@ object GeneratorPy { val hasConverterFiles: B = (converterFiles.size > 0) val top_level_package_nameT: String = genPyPackageName(modelName) + files = files ++ converterFiles files = files ++ genPyNodeFiles(modelName, threadComponents, connectionMap, datatypeMap, hasConverterFiles, strictAADLMode, invertTopicBinding, reporter) From ad976ddba4f4e34fc38ae9675acd2152f7fac7d8 Mon Sep 17 00:00:00 2001 From: Cat Date: Fri, 7 Mar 2025 15:09:00 -0600 Subject: [PATCH 08/18] Acceptor methods and Python launch files --- .../hamr/codegen/ros2/GeneratorPy.scala | 163 ++++++++++-------- .../hamr/codegen/ros2/Ros2Codegen.scala | 2 +- 2 files changed, 90 insertions(+), 75 deletions(-) diff --git a/shared/src/main/scala/org/sireum/hamr/codegen/ros2/GeneratorPy.scala b/shared/src/main/scala/org/sireum/hamr/codegen/ros2/GeneratorPy.scala index d59e544d..2ec7e805 100644 --- a/shared/src/main/scala/org/sireum/hamr/codegen/ros2/GeneratorPy.scala +++ b/shared/src/main/scala/org/sireum/hamr/codegen/ros2/GeneratorPy.scala @@ -137,11 +137,11 @@ object GeneratorPy { // "ts_exe = tc_py_pkg.ts_src:main" def genPySetupEntryPointDecl(modelName: String, componentName: String): ST = { - val node_source_file_nameT = genPyNodeSourceName(componentName) + val node_source_file_nameT = st"${componentName}_runner" val py_package_nameT = genPyPackageName(modelName) val node_executable_file_nameT = genExecutableFileName(componentName) val entryPointDecl: ST - = st"\"${node_executable_file_nameT} = ${py_package_nameT}.${node_source_file_nameT}:${py_src_node_entry_point_name}\"" + = st"\"${node_executable_file_nameT} = ${py_package_nameT}.user_code.${node_source_file_nameT}:${py_src_node_entry_point_name}\"" return entryPointDecl } @@ -154,9 +154,9 @@ object GeneratorPy { // build entry point declarations var entry_point_decls: ISZ[ST] = IS() for (comp <- threadComponents) { - val launch_node_decl_nameT = genPyFormatLaunchNodeDeclName(comp.pathAsString("_")) + val launch_node_decl_nameT = genPyFormatLaunchNodeDeclName(genNodeName(comp)) entry_point_decls = - entry_point_decls :+ genPySetupEntryPointDecl(modelName, comp.pathAsString("_")) + entry_point_decls :+ genPySetupEntryPointDecl(modelName, genNodeName(comp)) } val setupFileBody = @@ -228,6 +228,9 @@ object GeneratorPy { val top_level_package_nameT: String = genPyPackageName(modelName) val fileName: String = "package.xml" + val startMarker: String = "" + val endMarker: String = "" + val packages: ISZ[String] = IS(s"${genPyPackageName(modelName)}_interfaces") val pkgDependencies: ISZ[ST] = genPackageFilePkgDependencies(packages) @@ -244,6 +247,10 @@ object GeneratorPy { | rclpy | ${(pkgDependencies, "\n")} | + | ${startMarker} + | + | ${endMarker} + | | ament_copyright | ament_flake8 | ament_pep257 @@ -410,11 +417,10 @@ object GeneratorPy { // L a u n c h File Setup Files //================================================ - def genLaunchCMakeListsFile(modelName: String): (ISZ[String], ST) = { + def genLaunchCMakeListsFile(modelName: String): (ISZ[String], ST, B, ISZ[Marker]) = { val top_level_package_nameT: String = genPyPackageName(modelName) val fileName: String = "CMakeLists.txt" - // TODO: Add 'find interface type packages' under rclcpp val setupFileBody = st"""cmake_minimum_required(VERSION 3.8) |project(${top_level_package_nameT}_bringup) @@ -435,19 +441,16 @@ object GeneratorPy { val filePath: ISZ[String] = IS("src", s"${top_level_package_nameT}_bringup", fileName) - return (filePath, setupFileBody) + return (filePath, setupFileBody, true, IS()) } - def genLaunchPackageFile(modelName: String): (ISZ[String], ST) = { + def genLaunchPackageFile(modelName: String): (ISZ[String], ST, B, ISZ[Marker]) = { val top_level_package_nameT: String = genPyPackageName(modelName) val fileName: String = "package.xml" - // TODO: This list of message types is currently just a placeholder - // It should probably be built up while looping through each port - val packages: ISZ[String] = IS("example_interfaces") - val pkgDependencies: ISZ[ST] = genPackageFilePkgDependencies(packages) + val startMarker: String = "" + val endMarker: String = "" - // TODO: Add interface type package dependencies under rclcpp val setupFileBody = st""" | @@ -461,6 +464,11 @@ object GeneratorPy { | ament_cmake | | ${top_level_package_nameT} + | ${top_level_package_nameT}_interfaces + | + | ${startMarker} + | + | ${endMarker} | | ament_lint_auto | ament_lint_common @@ -473,7 +481,7 @@ object GeneratorPy { val filePath: ISZ[String] = IS("src", s"${top_level_package_nameT}_bringup", fileName) - return (filePath, setupFileBody) + return (filePath, setupFileBody, true, IS()) } @@ -501,12 +509,11 @@ object GeneratorPy { def genPyFormatLaunchNodeDecl(launch_node_decl_nameT: String, top_level_package_nameT: String, component: AadlThread): ST = { - val node_executable_file_nameT = genExecutableFileName(component.pathAsString("_")) + val node_executable_file_nameT = genExecutableFileName(genNodeName(component)) val s = - st""" - |${launch_node_decl_nameT} = Node( - | package = ${top_level_package_nameT}, - | executable = ${node_executable_file_nameT} + st"""${launch_node_decl_nameT} = Node( + | package = "${top_level_package_nameT}", + | executable = "${node_executable_file_nameT}" | ) """ return s @@ -520,7 +527,7 @@ object GeneratorPy { } // For example, see https://github.com/santoslab/ros-examples/blob/main/tempControl_ws/src/tc_bringup/launch/tc.launch.py - def genPyFormatLaunchFile(modelName: String, threadComponents: ISZ[AadlThread]): (ISZ[String], ST) = { + def genPyFormatLaunchFile(modelName: String, threadComponents: ISZ[AadlThread]): (ISZ[String], ST, B, ISZ[Marker]) = { val fileName = genPyLaunchFileName(modelName) val top_level_package_nameT: String = genPyPackageName(modelName) @@ -529,7 +536,7 @@ object GeneratorPy { var ld_entries: ISZ[ST] = IS() for (comp <- threadComponents) { - val launch_node_decl_nameT = genPyFormatLaunchNodeDeclName(comp.pathAsString("_")) + val launch_node_decl_nameT = genPyFormatLaunchNodeDeclName(genNodeName(comp)) node_decls = node_decls :+ genPyFormatLaunchNodeDecl(launch_node_decl_nameT, top_level_package_nameT, comp) ld_entries = ld_entries :+ genPyFormatLaunchAddAction(launch_node_decl_nameT) } @@ -547,9 +554,9 @@ object GeneratorPy { | return ld """ - val filePath: ISZ[String] = IS("src", s"${modelName}_bringup", "launch", fileName) + val filePath: ISZ[String] = IS("src", s"${top_level_package_nameT}_bringup", "launch", fileName) - return (filePath, launchFileBody) + return (filePath, launchFileBody, true, IS()) } //================================================ @@ -703,11 +710,11 @@ object GeneratorPy { val handlerName = inPort.identifier val thread: ST = - st"""def ${portType}_thread(self): + st"""def ${handlerName}_thread(self): | with self.lock_: - | self.receiveInputs(infrastructureIn_${handlerName}, applicationIn_${handlerName}) - | if (applicationIn_${handlerName}.empty()) return - | self.handle_${handlerName}_base(applicationIn_${handlerName}.front()) + | self.receiveInputs(self.infrastructureIn_${handlerName}, self.applicationIn_${handlerName}) + | if self.applicationIn_${handlerName}.empty(): return + | self.handle_${handlerName}_base(self.applicationIn_${handlerName}.front()) | self.applicationIn_${handlerName}.pop() | self.sendOutputs() """ @@ -721,17 +728,19 @@ object GeneratorPy { val handler: ST = if (!isSporadic || inPort.isInstanceOf[AadlDataPort]) st"self.enqueue(infrastructureIn_${handlerName}, msg)" else - st"""self.enqueue(infrastructureIn_${handlerName}, msg); - | thread = threading.Thread(target=${portType}_thread) - | thread.daemon = True - | thread.start() - """ + st"""self.enqueue(self.infrastructureIn_${handlerName}, msg) + |thread = threading.Thread(target=${handlerName}_thread) + |thread.daemon = True + |thread.start() + """ - return st"""def accept_${handlerName}(self, ${portType} msg): - | typedMsg = ${portType}() - | typedMsg.data = msg - | ${handler} - |""" + val method: ST = + st"""def accept_${handlerName}(self, msg): + | typedMsg = ${portType}() + | typedMsg.data = msg + | ${handler} + """ + return method } def genPyInfrastructureInQueue(inPort: AadlPort): ST = { @@ -774,11 +783,11 @@ object GeneratorPy { } else { handlerCode = - st"""void handle_${handlerName}_base(self, msg): + st"""def handle_${handlerName}_base(self, msg): | if type(msg) is ${portType}: | typedMsg = ${portType}() | typedMsg.data = msg - | handle_${handlerName}(typedMsg) + | self.handle_${handlerName}(typedMsg) | else: | self.get_logger.error("Receiving wrong type of variable on port ${handlerName}.\nThis shouldn't be possible. If you are seeing this message, please notify this tool's current maintainer.") |""" @@ -1228,12 +1237,10 @@ object GeneratorPy { } if (isSporadic(component) && !p.isInstanceOf[AadlDataPort]) { - strictSubscriptionMessageAcceptorMethods = strictSubscriptionMessageAcceptorMethods :+ - genPyThread(p, portDatatype) + strictSubscriptionMessageAcceptorMethods = strictSubscriptionMessageAcceptorMethods :+ genPyThread(p, portDatatype) } - strictSubscriptionMessageAcceptorMethods = strictSubscriptionMessageAcceptorMethods :+ - genPyMessageAcceptor(p, isSporadic(component), portDatatype) + strictSubscriptionMessageAcceptorMethods = strictSubscriptionMessageAcceptorMethods :+ genPyMessageAcceptor(p, isSporadic(component), portDatatype) inMsgVars = inMsgVars :+ genPyInfrastructureInQueue(p) inMsgVars = inMsgVars :+ genPyApplicationInQueue(p) @@ -1408,32 +1415,32 @@ object GeneratorPy { | ${genPyOutPortTupleVector(outPortNames)}""" } - if (subscriberMethods.size > 0 || publisherMethods.size > 0) { + if (inMsgVars.size > 0) { fileBody = st"""${fileBody} - |#================================================= - |# C o m m u n i c a t i o n - |#================================================= + | ${(inMsgVars, "\n")} """ + } - if (inMsgVars.size > 0) { - fileBody = - st"""${fileBody} - | ${(inMsgVars, "\n")} + if (outMsgVars.size > 0) { + fileBody = + st"""${fileBody} + | ${(outMsgVars, "\n")} """ - } + } - if (outMsgVars.size > 0) { - fileBody = - st"""${fileBody} - | ${(outMsgVars, "\n")} + if (subscriberMethods.size > 0 || publisherMethods.size > 0 || (strictAADLMode && subscribers.size > 0)) { + fileBody = + st"""${fileBody} + |#================================================= + |# C o m m u n i c a t i o n + |#================================================= """ - } if (strictSubscriptionMessageAcceptorMethods.size > 0) { fileBody = st"""${fileBody} - |${(strictSubscriptionMessageAcceptorMethods, "\n")}""" + | ${(strictSubscriptionMessageAcceptorMethods, "\n")}""" } if (subscriberMethods.size > 0) { @@ -1490,13 +1497,13 @@ object GeneratorPy { var subscriptionHandlerHeader: ST = st"" if (isEventPort(portType)) { subscriptionHandlerHeader = - st"""def handle_${handlerName}(self): + st"""def handle_${handlerName}(): | pass # Handle ${handlerName} event """ } else { subscriptionHandlerHeader = - st"""def handle_${handlerName}(self, msg): + st"""def handle_${handlerName}(msg): | pass # Handle ${handlerName} msg """ } @@ -1535,6 +1542,8 @@ object GeneratorPy { val nodeName = genNodeName(component) val fileName = genPyNodeSourceName(nodeName) + //TODO: Markers + var subscriptionHandlers: ISZ[ST] = IS() if (isSporadic(component)) { for (p <- component.getPorts()) { @@ -1555,13 +1564,21 @@ object GeneratorPy { subscriptionHandlers = subscriptionHandlers :+ genPyTimeTriggeredMethod() } - //TODO: converter files - - val fileBody = + var includeFiles: ST = st"""#!/usr/bin/env python3 |import rclpy |from rclpy.node import Node - |from ${packageName}.base_code.${nodeName}_runner import ${nodeName} + |from ${packageName}.base_code.${nodeName}_runner import ${nodeName}""" + + if (hasConverterFiles) { + includeFiles = + st"""${includeFiles} + |from ${packageName}.base_code.enum_converter import *""" + } + + val fileBody = + st"""${includeFiles} + | |#=========================================================== |# This file will not be overwritten when re-running Codegen |#=========================================================== @@ -1649,7 +1666,7 @@ object GeneratorPy { return py_files } - def genPyEnumConverters(packageName: String, enumTypes: ISZ[(String, AadlType)], strictAADLMode: B): ISZ[ST] = { + def genPyEnumConverters(enumTypes: ISZ[(String, AadlType)], strictAADLMode: B): ISZ[ST] = { var converters: ISZ[ST] = IS() for (enum <- enumTypes) { @@ -1670,7 +1687,7 @@ object GeneratorPy { st"""def enumToString(value): | typedValue = ${enumName}() | typedValue.data = value - | match (typedValue.${enum._1}) { + | match (typedValue.${enum._1}): | ${(cases, "\n")} | case default: | return "Unknown value for ${enumName}" @@ -1695,13 +1712,14 @@ object GeneratorPy { def genPyEnumConverterFile(packageName: String, enumTypes: ISZ[(String, AadlType)], strictAADLMode: B): (ISZ[String], ST, B, ISZ[Marker]) = { val fileBody = - st"""from datatypes_system_py_pkg_interfaces.msg import MyEnum + st"""#!/usr/bin/env python3 + |from datatypes_system_py_pkg_interfaces.msg import MyEnum | |#======================================================== |# Re-running Codegen will overwrite changes to this file |#======================================================== | - |${(genPyEnumConverters(packageName, enumTypes, strictAADLMode), "\n")} + |${(genPyEnumConverters(enumTypes, strictAADLMode), "\n")} """ val filePath: ISZ[String] = IS("src", packageName, packageName, "base_code", "enum_converter.py") @@ -1765,14 +1783,11 @@ object GeneratorPy { return files } - // TODO: Python pkgs - def genPyLaunchPkg(modelName: String, threadComponents: ISZ[AadlThread]): ISZ[(ISZ[String], ST)] = { - var files: ISZ[(ISZ[String], ST)] = IS() - - // TODO - //files = files :+ genXmlFormatLaunchFile(modelName, threadComponents) + def genPyLaunchPkg(modelName: String, threadComponents: ISZ[AadlThread]): ISZ[(ISZ[String], ST, B, ISZ[Marker])] = { + var files: ISZ[(ISZ[String], ST, B, ISZ[Marker])] = IS() files = files :+ genLaunchCMakeListsFile(modelName) files = files :+ genLaunchPackageFile(modelName) + files = files :+ genPyFormatLaunchFile(modelName, threadComponents) return files } diff --git a/shared/src/main/scala/org/sireum/hamr/codegen/ros2/Ros2Codegen.scala b/shared/src/main/scala/org/sireum/hamr/codegen/ros2/Ros2Codegen.scala index e36a0ead..c7f065a0 100644 --- a/shared/src/main/scala/org/sireum/hamr/codegen/ros2/Ros2Codegen.scala +++ b/shared/src/main/scala/org/sireum/hamr/codegen/ros2/Ros2Codegen.scala @@ -56,7 +56,7 @@ object Ros2Codegen { options.ros2LaunchLanguage.name match { case "Xml" => files = files ++ Generator.genXmlLaunchPkg(modelName, threadComponents, systemComponents) - case "Python" => files = files ++ Generator.genPyLaunchPkg(modelName, threadComponents) + case "Python" => files = files ++ GeneratorPy.genPyLaunchPkg(modelName, threadComponents) case _ => reporter.error(None(), toolName, s"Unknown code type: ${options.ros2NodesLanguage.name}") } From 37c5d568e37c855058d927146f8465437ea3b5ae Mon Sep 17 00:00:00 2001 From: CatLiSantos Date: Thu, 13 Mar 2025 15:00:18 -0500 Subject: [PATCH 09/18] Added Markers --- .../hamr/codegen/ros2/GeneratorPy.scala | 32 ++++++++++++------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/shared/src/main/scala/org/sireum/hamr/codegen/ros2/GeneratorPy.scala b/shared/src/main/scala/org/sireum/hamr/codegen/ros2/GeneratorPy.scala index 2ec7e805..b87ed4c6 100644 --- a/shared/src/main/scala/org/sireum/hamr/codegen/ros2/GeneratorPy.scala +++ b/shared/src/main/scala/org/sireum/hamr/codegen/ros2/GeneratorPy.scala @@ -264,7 +264,7 @@ object GeneratorPy { val filePath: ISZ[String] = IS("src", top_level_package_nameT, fileName) - return (filePath, setupFileBody, true, IS()) + return (filePath, setupFileBody, F, IS(Marker(startMarker, endMarker))) } def genPyInitFile(packageName: String): (ISZ[String], ST, B, ISZ[Marker]) = { @@ -276,7 +276,7 @@ object GeneratorPy { val filePath: ISZ[String] = IS("src", packageName, packageName, fileName) - return (filePath, fileBody, true, IS()) + return (filePath, fileBody, T, IS()) } def genPySubInitFile(modelName: String, subModelName: String): (ISZ[String], ST, B, ISZ[Marker]) = { @@ -289,7 +289,7 @@ object GeneratorPy { val filePath: ISZ[String] = IS("src", top_level_package_nameT, top_level_package_nameT, subModelName, fileName) - return (filePath, setupFileBody, true, IS()) + return (filePath, setupFileBody, T, IS()) } def genPyResourceFile(modelName: String): (ISZ[String], ST, B, ISZ[Marker]) = { @@ -301,7 +301,7 @@ object GeneratorPy { val filePath: ISZ[String] = IS("src", top_level_package_nameT, "resource", top_level_package_nameT) - return (filePath, setupFileBody, true, IS()) + return (filePath, setupFileBody, T, IS()) } def genPyCopyrightFile(modelName: String): (ISZ[String], ST, B, ISZ[Marker]) = { @@ -338,7 +338,7 @@ object GeneratorPy { val filePath: ISZ[String] = IS("src", top_level_package_nameT, "test", fileName) - return (filePath, setupFileBody, true, IS()) + return (filePath, setupFileBody, T, IS()) } def genPyFlakeFile(modelName: String): (ISZ[String], ST, B, ISZ[Marker]) = { @@ -375,7 +375,7 @@ object GeneratorPy { val filePath: ISZ[String] = IS("src", top_level_package_nameT, "test", fileName) - return (filePath, setupFileBody, true, IS()) + return (filePath, setupFileBody, T, IS()) } def genPyPrepFile(modelName: String): (ISZ[String], ST, B, ISZ[Marker]) = { @@ -410,7 +410,7 @@ object GeneratorPy { val filePath: ISZ[String] = IS("src", top_level_package_nameT, "test", fileName) - return (filePath, setupFileBody, true, IS()) + return (filePath, setupFileBody, T, IS()) } //================================================ @@ -441,7 +441,7 @@ object GeneratorPy { val filePath: ISZ[String] = IS("src", s"${top_level_package_nameT}_bringup", fileName) - return (filePath, setupFileBody, true, IS()) + return (filePath, setupFileBody, T, IS()) } def genLaunchPackageFile(modelName: String): (ISZ[String], ST, B, ISZ[Marker]) = { @@ -481,7 +481,7 @@ object GeneratorPy { val filePath: ISZ[String] = IS("src", s"${top_level_package_nameT}_bringup", fileName) - return (filePath, setupFileBody, true, IS()) + return (filePath, setupFileBody, F, IS(Marker(startMarker, endMarker))) } @@ -556,7 +556,7 @@ object GeneratorPy { val filePath: ISZ[String] = IS("src", s"${top_level_package_nameT}_bringup", "launch", fileName) - return (filePath, launchFileBody, true, IS()) + return (filePath, launchFileBody, T, IS()) } //================================================ @@ -1542,7 +1542,8 @@ object GeneratorPy { val nodeName = genNodeName(component) val fileName = genPyNodeSourceName(nodeName) - //TODO: Markers + val startMarker: String = "# Additions within these tags will be preserved when re-running Codegen" + val endMarker: String = "# Additions within these tags will be preserved when re-running Codegen" var subscriptionHandlers: ISZ[ST] = IS() if (isSporadic(component)) { @@ -1597,11 +1598,18 @@ object GeneratorPy { |# C o m p u t e E n t r y P o i n t |#================================================= |${(subscriptionHandlers, "\n")} + | + |#================================================= + |# Include any additional declarations here + |#================================================= + |${startMarker} + | + |${endMarker} """ val filePath: ISZ[String] = IS("src", packageName, packageName, "user_code", fileName) - return (filePath, fileBody, F, IS()) + return (filePath, fileBody, F, IS(Marker(startMarker, endMarker))) } def genPyNodeRunnerName(compNameS: String): String = { From 8dadb04f1c0c618272e4179d4d0838f1ebbe5838 Mon Sep 17 00:00:00 2001 From: CatLiSantos Date: Thu, 10 Apr 2025 12:37:35 -0500 Subject: [PATCH 10/18] Fixed imports and ros2 compile errors --- .DS_Store | Bin 0 -> 6148 bytes jvm/.DS_Store | Bin 0 -> 6148 bytes jvm/src/.DS_Store | Bin 0 -> 6148 bytes .../hamr/codegen/ros2/GeneratorPy.scala | 71 +++++++++++------- 4 files changed, 43 insertions(+), 28 deletions(-) create mode 100644 .DS_Store create mode 100644 jvm/.DS_Store create mode 100644 jvm/src/.DS_Store diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..274040f32e6ca099625b79613bce0330203ce64d GIT binary patch literal 6148 zcmeHK&yUhT6n?WH6j(QyXtEcRCSD05?ryU2g2)~`cv*}d)F9n9OV`pNP!JPALZcP57T znu$@ME4rp}W<*vy`J}X$vzSfv_xOi13H({qYW);@h2ou3*(zH#>qCDe7yW1$&1c== z^d0wJOBpzu_Rx9bdy7H+{tFpKLqGJUir~8*T;9L+Lsu@kavr)f#qGy8tcq0`)DM@- ziuK^p<7elCPr*2pUodl+!2Y4^uEq`U`t@qA`_nLx zA?FHvSD~;Ed`CUj`^ufNJc7QT+Se5doF8OiKH*OH#DueRlDIhhHexv3fw*g`1@d^Fm^Pq zV^p6GRO$!-EaI{wkzLPf?n zI9Ea{(k;XlT6PjU=+Aj3W#E_-|J#YdT-4w yj`vy{kVC6jXXURt?^Y4^foip3MzlN8>t1T!EQC0!ju`83q2S0)GII+z#ac literal 0 HcmV?d00001 diff --git a/jvm/.DS_Store b/jvm/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..44c6ad9edc7487d039a2aac484abf3b8f352b40d GIT binary patch literal 6148 zcmeHK!AiqG5Z!I7O(;SR3OxqA7Ok}vikBGc!K)EHsMMq>8jM+KQgbMUob`wN5`RZ$ zb~i$)UOWhv8JK;W*_jRVHtb{=W89sFdyH9(F#{B_V#4r^;5zD(RJ5fE$Z-!b3Stq! z6K+TH9~i*1tFn|$*yuD~IKLxU+4+zn6VYBlo<8YQehZ%BD9zgKcV4O1*4FDr-Dns$ z-o41Z%+IE&>rbxm>0F37DCd4~5smYqwRtR(%#V`sL$dj>vUUor`Mmi>{-9lZrR=agZbPvwzhW<&xQ~2VCU*LkVayF7+7Qgdp{5h>;K8m{l9dgffyhLR+9l<8+Zd3a?`bSDNC%iHs~!V3i=fa mzd~RlOELIjDQ<#F0lSS8Ku2St5G)|{BOqy@ff)Ex20j5gic;(V literal 0 HcmV?d00001 diff --git a/jvm/src/.DS_Store b/jvm/src/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..869e8b1a9076e8dd28eec89ad645ea3ad46f44ca GIT binary patch literal 6148 zcmeHK!EVz)5S?w^)}gAD1Bf11;u=8+4Jhixg!aG{SLFaGwMhgPuD6OEa+?W zg7q@M8fE=l=Z41N*Zf+q{Vb&a55-_Hg*yt5=$<$T8p;_)iA-evnYcz+z=kUmY;I z1ps 0) { + stdIncludes = + st"""${stdIncludes} + |from ${packageName}.user_code.consumer_consumer_src import *""" + } + var fileBody = st"""#!/usr/bin/env python3 |import rclpy @@ -1394,6 +1408,10 @@ object GeneratorPy { | ${genPyTimeTriggeredTimer(component)} """ } + + fileBody = + st"""${fileBody} + | ${genPyTimeTriggeredBaseMethod()}""" } if(strictAADLMode) { @@ -1409,24 +1427,24 @@ object GeneratorPy { | ${genPyInEventPortTupleVector(inPortNames)}""" } - fileBody = - st"""${fileBody} - | # Used by sendOutputs - | ${genPyOutPortTupleVector(outPortNames)}""" - } + if (inMsgVars.size > 0) { + fileBody = + st"""${fileBody} + | ${(inMsgVars, "\n")} + """ + } - if (inMsgVars.size > 0) { - fileBody = - st"""${fileBody} - | ${(inMsgVars, "\n")} + if (outMsgVars.size > 0) { + fileBody = + st"""${fileBody} + | ${(outMsgVars, "\n")} """ - } + } - if (outMsgVars.size > 0) { fileBody = st"""${fileBody} - | ${(outMsgVars, "\n")} - """ + | # Used by sendOutputs + | ${genPyOutPortTupleVector(outPortNames)}""" } if (subscriberMethods.size > 0 || publisherMethods.size > 0 || (strictAADLMode && subscribers.size > 0)) { @@ -1568,8 +1586,7 @@ object GeneratorPy { var includeFiles: ST = st"""#!/usr/bin/env python3 |import rclpy - |from rclpy.node import Node - |from ${packageName}.base_code.${nodeName}_runner import ${nodeName}""" + |from rclpy.node import Node""" if (hasConverterFiles) { includeFiles = @@ -1587,9 +1604,7 @@ object GeneratorPy { |#================================================= |# I n i t i a l i z e E n t r y P o i n t |#================================================= - |def initialize(n): - | node = ${nodeName}() - | node.data = n + |def initialize(node): | node.get_logger().info("Initialize Entry Point invoked") | | # Initialize the node @@ -1627,8 +1642,8 @@ object GeneratorPy { |import rclpy |from rclpy.node import Node |from rclpy.executors import MultiThreadedExecutor - |from ${packageName}.user_code.${nodeName}_src import * - |from ${packageName}.user_code.${nodeName}_base_src import ${nodeName}_base + |from ${packageName}.user_code.${nodeName}_src import initialize + |from ${packageName}.base_code.${nodeName}_base_src import ${nodeName}_base |#======================================================== |# Re-running Codegen will overwrite changes to this file |#======================================================== @@ -1637,7 +1652,7 @@ object GeneratorPy { | def __init__(self): | super().__init__() | # invoke initialize entry point - | initialize() + | initialize(self) | | self.get_logger().info("${nodeName} infrastructure set up") | From b9588fdf0777272c50690ee27b6144ac8ca09830 Mon Sep 17 00:00:00 2001 From: CatLiSantos Date: Wed, 16 Apr 2025 16:45:30 -0500 Subject: [PATCH 11/18] Reworking launch files --- .DS_Store | Bin 6148 -> 6148 bytes jvm/.DS_Store | Bin 6148 -> 6148 bytes jvm/src/.DS_Store | Bin 6148 -> 6148 bytes .../hamr/codegen/ros2/GeneratorPy.scala | 53 +++++++++--------- 4 files changed, 27 insertions(+), 26 deletions(-) diff --git a/.DS_Store b/.DS_Store index 274040f32e6ca099625b79613bce0330203ce64d..fa4c1aed6333c67c0abed74328fa17d82ea2911f 100644 GIT binary patch delta 21 ccmZoMXffE3!Ny@>Y@wrIVr00vfNhE(07Ll&;{X5v delta 21 ccmZoMXffE3!Ny@~WTK;BWNNv&fNhE(07K6O^-5he#w07;z&GXMYp delta 21 ccmZoMXffEZhl#_~$V5lM$kcN45he#w07-KOGynhq diff --git a/jvm/src/.DS_Store b/jvm/src/.DS_Store index 869e8b1a9076e8dd28eec89ad645ea3ad46f44ca..dddd78ed031e6eb6c445ff44b5c4e3e2ba275086 100644 GIT binary patch delta 21 ccmZoMXffDuo|VJG*g{9a#K>^-byi&=081tYNdN!< delta 21 ccmZoMXffDuo|VJY$V5lM$kcN4byi&=080D@N&o-= diff --git a/shared/src/main/scala/org/sireum/hamr/codegen/ros2/GeneratorPy.scala b/shared/src/main/scala/org/sireum/hamr/codegen/ros2/GeneratorPy.scala index 99a09040..05311396 100644 --- a/shared/src/main/scala/org/sireum/hamr/codegen/ros2/GeneratorPy.scala +++ b/shared/src/main/scala/org/sireum/hamr/codegen/ros2/GeneratorPy.scala @@ -4,7 +4,7 @@ package org.sireum.hamr.codegen.ros2 import org.sireum._ import org.sireum.hamr.codegen.common.containers.Marker -import org.sireum.hamr.codegen.common.symbols.{AadlDataPort, AadlEventDataPort, AadlPort, AadlThread, Dispatch_Protocol} +import org.sireum.hamr.codegen.common.symbols.{AadlDataPort, AadlEventDataPort, AadlPort, AadlSystem, AadlThread, Dispatch_Protocol} import org.sireum.hamr.codegen.common.types.{AadlType, EnumType} import org.sireum.hamr.ir.Direction import org.sireum.message.Reporter @@ -154,7 +154,6 @@ object GeneratorPy { // build entry point declarations var entry_point_decls: ISZ[ST] = IS() for (comp <- threadComponents) { - val launch_node_decl_nameT = genPyFormatLaunchNodeDeclName(genNodeName(comp)) entry_point_decls = entry_point_decls :+ genPySetupEntryPointDecl(modelName, genNodeName(comp)) } @@ -527,36 +526,38 @@ object GeneratorPy { } // For example, see https://github.com/santoslab/ros-examples/blob/main/tempControl_ws/src/tc_bringup/launch/tc.launch.py - def genPyFormatLaunchFile(modelName: String, threadComponents: ISZ[AadlThread]): (ISZ[String], ST, B, ISZ[Marker]) = { - val fileName = genPyLaunchFileName(modelName) + def genPyFormatLaunchFile(modelName: String, threadComponents: ISZ[AadlThread], + systemComponents: ISZ[AadlSystem]): ISZ[(ISZ[String], ST, B, ISZ[Marker])] = { val top_level_package_nameT: String = genPyPackageName(modelName) - var node_decls: ISZ[ST] = IS() - var ld_entries: ISZ[ST] = IS() + var launchFiles: ISZ[(ISZ[String], ST, B, ISZ[Marker])] = IS() + + for (system <- systemComponents) { + val fileName = genPyFormatLaunchNodeDeclName(system.identifier) + + val node_decls: ISZ[ST] = IS() + val ld_entries: ISZ[ST] = IS() + + val launchFileBody = + st"""from launch import LaunchDescription + |from launch_ros.actions import Node + | + |def generate_launch_description(): + | ld = LaunchDescription() + | + | ${(node_decls, "\n")} + | ${(ld_entries, "\n")} + | + | return ld + """ - for (comp <- threadComponents) { - val launch_node_decl_nameT = genPyFormatLaunchNodeDeclName(genNodeName(comp)) - node_decls = node_decls :+ genPyFormatLaunchNodeDecl(launch_node_decl_nameT, top_level_package_nameT, comp) - ld_entries = ld_entries :+ genPyFormatLaunchAddAction(launch_node_decl_nameT) - } + val filePath: ISZ[String] = IS("src", s"${top_level_package_nameT}_bringup", "launch", fileName) - val launchFileBody = - st"""from launch import LaunchDescription - |from launch_ros.actions import Node - | - |def generate_launch_description(): - | ld = LaunchDescription() - | - | ${(node_decls, "\n")} - | ${(ld_entries, "\n")} - | - | return ld - """ - - val filePath: ISZ[String] = IS("src", s"${top_level_package_nameT}_bringup", "launch", fileName) + launchFiles = launchFiles :+ (filePath, launchFileBody, T, IS()) + } - return (filePath, launchFileBody, T, IS()) + return launchFiles } //================================================ From 309989429de40cc0b861a15092e08ec0ef9386d7 Mon Sep 17 00:00:00 2001 From: CatLiSantos Date: Thu, 1 May 2025 13:16:37 -0500 Subject: [PATCH 12/18] Added Virtual Methods --- .DS_Store | Bin 6148 -> 6148 bytes jvm/.DS_Store | Bin 6148 -> 6148 bytes jvm/src/.DS_Store | Bin 6148 -> 6148 bytes .../hamr/codegen/ros2/GeneratorPy.scala | 177 +++++++++++++++--- .../hamr/codegen/ros2/Ros2Codegen.scala | 2 +- 5 files changed, 150 insertions(+), 29 deletions(-) diff --git a/.DS_Store b/.DS_Store index fa4c1aed6333c67c0abed74328fa17d82ea2911f..27283bb65900e05a42b459e779c51ae19a75914f 100644 GIT binary patch delta 21 ccmZoMXffE3!Ny@~VWFd7Vrag(fNhE(07NSV>Hq)$ delta 21 ccmZoMXffE3!Ny@>Y@wrIVr00vfNhE(07Ll&;{X5v diff --git a/jvm/.DS_Store b/jvm/.DS_Store index 9a2b8d1ce5a73af93acd572d7a607b491f25b720..89a693a07aed4bb8be889710b84c4b946391679a 100644 GIT binary patch delta 21 ccmZoMXffEZhl#_~!a_&E#L#^65he#w07=gVIsgCw delta 21 ccmZoMXffEZhl#_&*g{9a#K>^-5he#w07;z&GXMYp diff --git a/jvm/src/.DS_Store b/jvm/src/.DS_Store index dddd78ed031e6eb6c445ff44b5c4e3e2ba275086..e0f15ce9858ec8880a9137388eb0603140ae9370 100644 GIT binary patch delta 21 ccmZoMXffDuo|VJY!a_&E#L#^6byi&=083Z~Pyhe` delta 21 ccmZoMXffDuo|VJG*g{9a#K>^-byi&=081tYNdN!< diff --git a/shared/src/main/scala/org/sireum/hamr/codegen/ros2/GeneratorPy.scala b/shared/src/main/scala/org/sireum/hamr/codegen/ros2/GeneratorPy.scala index 05311396..40a61f65 100644 --- a/shared/src/main/scala/org/sireum/hamr/codegen/ros2/GeneratorPy.scala +++ b/shared/src/main/scala/org/sireum/hamr/codegen/ros2/GeneratorPy.scala @@ -4,7 +4,7 @@ package org.sireum.hamr.codegen.ros2 import org.sireum._ import org.sireum.hamr.codegen.common.containers.Marker -import org.sireum.hamr.codegen.common.symbols.{AadlDataPort, AadlEventDataPort, AadlPort, AadlSystem, AadlThread, Dispatch_Protocol} +import org.sireum.hamr.codegen.common.symbols.{AadlComponent, AadlDataPort, AadlEventDataPort, AadlPort, AadlProcess, AadlSystem, AadlThread, Dispatch_Protocol} import org.sireum.hamr.codegen.common.types.{AadlType, EnumType} import org.sireum.hamr.ir.Direction import org.sireum.message.Reporter @@ -505,24 +505,73 @@ object GeneratorPy { // package="tc_cpp_pkg", // executable="tc_exe" // ) - def genPyFormatLaunchNodeDecl(launch_node_decl_nameT: String, - top_level_package_nameT: String, - component: AadlThread): ST = { - val node_executable_file_nameT = genExecutableFileName(genNodeName(component)) + def genPyFormatLaunchNodeDecl(top_level_package_nameT: String, + thread: AadlThread): ST = { + val node_executable_file_nameT = genExecutableFileName(genNodeName(thread)) + val launch_node_decl_nameT = genPyFormatLaunchNodeDeclName(genNodeName(thread)) val s = st"""${launch_node_decl_nameT} = Node( | package = "${top_level_package_nameT}", | executable = "${node_executable_file_nameT}" - | ) + |) """ return s } + // Generate system launch code (including a system launch file) + // Example: + // + def genPyFormatLaunchSystemDecl(top_level_package_nameT: String, + system: AadlSystem): ST = { + val launch_node_decl_nameT = genPyFormatLaunchNodeDeclName(system.identifier) + val launchFileName: String = genPyLaunchFileName(system.identifier) + val s = + st"""${launch_node_decl_nameT} = IncludeLaunchDescription( + | PythonLaunchDescriptionSource( + | os.path.join(get_package_share_directory('${top_level_package_nameT}_bringup'), + | 'launch/${launchFileName}') + | ) + |) + """ + return s + } + + def genPyFormatLaunchDecls(component: AadlComponent, packageName: String): ISZ[ST] = { + var launch_decls: ISZ[ST] = IS() + + for (comp <- component.subComponents) { + comp match { + case thread: AadlThread => + launch_decls = launch_decls :+ genPyFormatLaunchNodeDecl(packageName, thread) + case system: AadlSystem => + launch_decls = launch_decls :+ genPyFormatLaunchSystemDecl(packageName, system) + case process: AadlProcess => + launch_decls = launch_decls ++ genPyFormatLaunchDecls(process, packageName) + case _ => + } + } + + return launch_decls + } + // Example: // ld.add_action(tc_node) - def genPyFormatLaunchAddAction(launch_node_decl_nameT: String): ST = { - val s = st"""ld.add_action(${launch_node_decl_nameT})""" - return s + def genPyFormatLaunchAddAction(component: AadlComponent): ISZ[ST] = { + var ld_entries: ISZ[ST] = IS() + + for(comp <- component.subComponents) { + comp match { + case thread: AadlThread => + ld_entries = ld_entries :+ st"""ld.add_action(${genPyFormatLaunchNodeDeclName(genNodeName(thread))})""" + case system: AadlSystem => + ld_entries = ld_entries :+ st"""ld.add_action(${genPyFormatLaunchNodeDeclName(system.identifier)})""" + case process: AadlProcess => + ld_entries = ld_entries ++ genPyFormatLaunchAddAction(process) + case _ => + } + } + + return ld_entries } // For example, see https://github.com/santoslab/ros-examples/blob/main/tempControl_ws/src/tc_bringup/launch/tc.launch.py @@ -534,15 +583,20 @@ object GeneratorPy { var launchFiles: ISZ[(ISZ[String], ST, B, ISZ[Marker])] = IS() for (system <- systemComponents) { - val fileName = genPyFormatLaunchNodeDeclName(system.identifier) + val fileName = genPyLaunchFileName(system.identifier) - val node_decls: ISZ[ST] = IS() - val ld_entries: ISZ[ST] = IS() + val node_decls: ISZ[ST] = genPyFormatLaunchDecls(system, top_level_package_nameT) + val ld_entries: ISZ[ST] = genPyFormatLaunchAddAction(system) val launchFileBody = st"""from launch import LaunchDescription |from launch_ros.actions import Node | + |import os + |from ament_index_python.packages import get_package_share_directory + |from launch.actions import IncludeLaunchDescription + |from launch.launch_description_sources import PythonLaunchDescriptionSource + | |def generate_launch_description(): | ld = LaunchDescription() | @@ -772,6 +826,25 @@ object GeneratorPy { return subscriptionMessageHeader } + def genPySubscriptionHandlerVirtualStrict(inPort: AadlPort, portType: String): ST = { + val handlerName = inPort.identifier + + var handlerCode: ST = st"" + if (isEventPort(portType)) { + handlerCode = + st"""def handle_${handlerName}(self): + | raise NotImplementedError("Subclasses must implement this method") + |""" + } else { + handlerCode = + st"""def handle_${handlerName}(self, msg): + | raise NotImplementedError("Subclasses must implement this method") + |""" + } + + return handlerCode + } + def genPySubscriptionHandlerBaseSporadic(inPort: AadlPort, portType: String): ST = { val handlerName = inPort.identifier @@ -905,7 +978,7 @@ object GeneratorPy { handler = st"self.event_handle_${handlerName}" } else { - handler = st"handle_${handlerName}" + handler = st"self.handle_${handlerName}" } if (outPortNames.size == 1) { @@ -986,6 +1059,25 @@ object GeneratorPy { return subscriptionMessageVar } + def genPySubscriptionHandlerVirtual(inPort: AadlPort, portType: String): ST = { + val handlerName = inPort.identifier + + var handlerCode: ST = st"" + if (isEventPort(portType)) { + handlerCode = + st"""def handle_${handlerName}(self): + | raise NotImplementedError("Subclasses must implement this method") + |""" + } else { + handlerCode = + st"""def handle_${handlerName}(self, msg): + | raise NotImplementedError("Subclasses must implement this method") + |""" + } + + return handlerCode + } + def genPyInfrastructureOutQueue(inPort: AadlPort): ST = { val portName = inPort.identifier @@ -1217,7 +1309,7 @@ object GeneratorPy { var inPortNames: ISZ[String] = IS() var strictPutMsgMethods: ISZ[ST] = IS() var strictSubscriptionMessageAcceptorMethods: ISZ[ST] = IS() - var strictSubscriptionHandlerBaseMethods: ISZ[ST] = IS() + var subscriptionHandlerMethods: ISZ[ST] = IS() var msgTypes: ISZ[String] = IS() var inMsgVars: ISZ[ST] = IS() @@ -1259,7 +1351,9 @@ object GeneratorPy { subscriptionMessageGetters = subscriptionMessageGetters :+ genPyGetApplicationInValue(p, portDatatype) } else { - strictSubscriptionHandlerBaseMethods = strictSubscriptionHandlerBaseMethods :+ + subscriptionHandlerMethods = subscriptionHandlerMethods :+ + genPySubscriptionHandlerVirtualStrict(p, portDatatype) + subscriptionHandlerMethods = subscriptionHandlerMethods :+ genPySubscriptionHandlerBaseSporadic(p, portDatatype) } hasInPorts = T @@ -1317,6 +1411,9 @@ object GeneratorPy { genPySubscriptionHandlerPeriodic(p, portDatatype) subscriptionMessageGetters = subscriptionMessageGetters :+ genPyGetSubscriptionMessage(p, nodeName) inMsgVars = inMsgVars :+ genPySubscriptionMessageVar(p) + } else { + subscriptionHandlerMethods = subscriptionHandlerMethods :+ + genPySubscriptionHandlerVirtual(p, portDatatype) } hasInPorts = T } @@ -1359,7 +1456,7 @@ object GeneratorPy { if (!strictAADLMode && subscribers.size > 0) { stdIncludes = st"""${stdIncludes} - |from ${packageName}.user_code.consumer_consumer_src import *""" + |from ${packageName}.user_code.${genNodeName(component)}_src import *""" } var fileBody = @@ -1451,9 +1548,9 @@ object GeneratorPy { if (subscriberMethods.size > 0 || publisherMethods.size > 0 || (strictAADLMode && subscribers.size > 0)) { fileBody = st"""${fileBody} - |#================================================= - |# C o m m u n i c a t i o n - |#================================================= + | #================================================= + | # C o m m u n i c a t i o n + | #================================================= """ if (strictSubscriptionMessageAcceptorMethods.size > 0) { @@ -1474,12 +1571,6 @@ object GeneratorPy { | ${(subscriptionMessageGetters, "\n")}""" } - if (strictSubscriptionHandlerBaseMethods.size > 0) { - fileBody = - st"""${fileBody} - | ${(strictSubscriptionHandlerBaseMethods, "\n")}""" - } - if (publisherMethods.size > 0) { fileBody = st"""${fileBody} @@ -1488,6 +1579,16 @@ object GeneratorPy { } } + if (subscriptionHandlerMethods.size > 0) { + fileBody = + st"""${fileBody} + | #================================================= + | # C o m p u t e E n t r y P o i n t + | #================================================= + | ${(subscriptionHandlerMethods, "\n")} + |""" + } + if (strictAADLMode) { if (!isSporadic(component)) { fileBody = @@ -1510,6 +1611,12 @@ object GeneratorPy { return (filePath, fileBody, T, IS()) } + def genPySubscriptionHandlerAdder(inPort: AadlPort): ST = { + val handlerName = inPort.identifier + val subscriptionAdder: ST = st"node.handle_${handlerName} = handle_${handlerName}" + return subscriptionAdder + } + def genPySubscriptionHandlerSporadicStrict(inPort: AadlPort, portType: String): ST = { val handlerName = inPort.identifier @@ -1565,6 +1672,7 @@ object GeneratorPy { val endMarker: String = "# Additions within these tags will be preserved when re-running Codegen" var subscriptionHandlers: ISZ[ST] = IS() + var subscriptionAdders: ISZ[ST] = IS() if (isSporadic(component)) { for (p <- component.getPorts()) { val portDatatype: String = genPortDatatype(p, packageName, datatypeMap, reporter) @@ -1572,16 +1680,21 @@ object GeneratorPy { if (strictAADLMode) { subscriptionHandlers = subscriptionHandlers :+ genPySubscriptionHandlerSporadicStrict(p, portDatatype) + subscriptionAdders = subscriptionAdders :+ + genPySubscriptionHandlerAdder(p) } else { subscriptionHandlers = subscriptionHandlers :+ genPySubscriptionHandlerSporadic(p, portDatatype) + subscriptionAdders = subscriptionAdders :+ + genPySubscriptionHandlerAdder(p) } } } } else { subscriptionHandlers = subscriptionHandlers :+ genPyTimeTriggeredMethod() + subscriptionAdders = subscriptionAdders :+ st"node.timeTriggered = timeTriggered" } var includeFiles: ST = @@ -1609,6 +1722,7 @@ object GeneratorPy { | node.get_logger().info("Initialize Entry Point invoked") | | # Initialize the node + | ${(subscriptionAdders, "\n")} | |#================================================= |# C o m p u t e E n t r y P o i n t @@ -1735,9 +1849,16 @@ object GeneratorPy { def genPyEnumConverterFile(packageName: String, enumTypes: ISZ[(String, AadlType)], strictAADLMode: B): (ISZ[String], ST, B, ISZ[Marker]) = { + var includes: ISZ[ST] = IS() + + for (enum <- enumTypes) { + val enumName: String = ops.StringOps(enum._2.classifier.apply(enum._2.classifier.size - 1)).replaceAllLiterally("_", "") + includes = includes :+ st"from ${packageName}_interfaces.msg import ${enumName}" + } + val fileBody = st"""#!/usr/bin/env python3 - |from datatypes_system_py_pkg_interfaces.msg import MyEnum + |${(includes, "\n")} | |#======================================================== |# Re-running Codegen will overwrite changes to this file @@ -1807,11 +1928,11 @@ object GeneratorPy { return files } - def genPyLaunchPkg(modelName: String, threadComponents: ISZ[AadlThread]): ISZ[(ISZ[String], ST, B, ISZ[Marker])] = { + def genPyLaunchPkg(modelName: String, threadComponents: ISZ[AadlThread], systemComponents: ISZ[AadlSystem]): ISZ[(ISZ[String], ST, B, ISZ[Marker])] = { var files: ISZ[(ISZ[String], ST, B, ISZ[Marker])] = IS() files = files :+ genLaunchCMakeListsFile(modelName) files = files :+ genLaunchPackageFile(modelName) - files = files :+ genPyFormatLaunchFile(modelName, threadComponents) + files = files ++ genPyFormatLaunchFile(modelName, threadComponents, systemComponents) return files } diff --git a/shared/src/main/scala/org/sireum/hamr/codegen/ros2/Ros2Codegen.scala b/shared/src/main/scala/org/sireum/hamr/codegen/ros2/Ros2Codegen.scala index c7f065a0..f2eb65fb 100644 --- a/shared/src/main/scala/org/sireum/hamr/codegen/ros2/Ros2Codegen.scala +++ b/shared/src/main/scala/org/sireum/hamr/codegen/ros2/Ros2Codegen.scala @@ -56,7 +56,7 @@ object Ros2Codegen { options.ros2LaunchLanguage.name match { case "Xml" => files = files ++ Generator.genXmlLaunchPkg(modelName, threadComponents, systemComponents) - case "Python" => files = files ++ GeneratorPy.genPyLaunchPkg(modelName, threadComponents) + case "Python" => files = files ++ GeneratorPy.genPyLaunchPkg(modelName, threadComponents, systemComponents) case _ => reporter.error(None(), toolName, s"Unknown code type: ${options.ros2NodesLanguage.name}") } From 5d5984044d7437f263bd862978414d57bc935bb7 Mon Sep 17 00:00:00 2001 From: CatLiSantos Date: Thu, 1 May 2025 16:15:46 -0500 Subject: [PATCH 13/18] Updated generation --- .DS_Store | Bin 6148 -> 6148 bytes jvm/.DS_Store | Bin 6148 -> 6148 bytes jvm/src/.DS_Store | Bin 6148 -> 6148 bytes .../hamr/codegen/ros2/GeneratorPy.scala | 40 ++++++++++-------- 4 files changed, 23 insertions(+), 17 deletions(-) diff --git a/.DS_Store b/.DS_Store index 27283bb65900e05a42b459e779c51ae19a75914f..72b00e533993cb9ed35aeaadec9160cf3762f803 100644 GIT binary patch delta 21 ccmZoMXffE3!Ny@?V6LNJY-G5(fNhE(07H%i+5i9m delta 21 ccmZoMXffE3!Ny@~VWFd7Vrag(fNhE(07NSV>Hq)$ diff --git a/jvm/.DS_Store b/jvm/.DS_Store index 89a693a07aed4bb8be889710b84c4b946391679a..573962ced56fca9bd889b03c44132f48b6e6fec1 100644 GIT binary patch delta 21 ccmZoMXffEZhl#_)z+6Yc*vN465he#w07)_iDgXcg delta 21 ccmZoMXffEZhl#_~!a_&E#L#^65he#w07=gVIsgCw diff --git a/jvm/src/.DS_Store b/jvm/src/.DS_Store index e0f15ce9858ec8880a9137388eb0603140ae9370..0fa0b30808a9b27c073a4aaf67769b8314d18686 100644 GIT binary patch delta 21 ccmZoMXffDuo|VJIz+6Yc*vN46byi&=07| 0) { fileBody = st"""${fileBody} @@ -1539,6 +1527,18 @@ object GeneratorPy { """ } + fileBody = + st"""${fileBody} + | # Used by receiveInputs + | ${genPyInDataPortTupleVector(inPortNames)}""" + + if (!isSporadic(component)) { + fileBody = + st"""${fileBody} + | # Used by receiveInputs + | ${genPyInEventPortTupleVector(inPortNames)}""" + } + fileBody = st"""${fileBody} | # Used by sendOutputs @@ -1571,6 +1571,12 @@ object GeneratorPy { | ${(subscriptionMessageGetters, "\n")}""" } + if (eventPortHandlers.size > 0) { + fileBody = + st"""${fileBody} + | ${(eventPortHandlers, "\n")}""" + } + if (publisherMethods.size > 0) { fileBody = st"""${fileBody} From 7269ca1259625a890beffd2041fd0adeb8baf422 Mon Sep 17 00:00:00 2001 From: CatLiSantos Date: Fri, 2 May 2025 11:59:06 -0500 Subject: [PATCH 14/18] Added a GeneratorLaunch file --- .../sireum/hamr/codegen/ros2/Generator.scala | 366 ------------------ .../hamr/codegen/ros2/GeneratorLaunch.scala | 327 ++++++++++++++++ .../hamr/codegen/ros2/GeneratorPy.scala | 228 ----------- .../hamr/codegen/ros2/Ros2Codegen.scala | 16 +- 4 files changed, 333 insertions(+), 604 deletions(-) create mode 100644 shared/src/main/scala/org/sireum/hamr/codegen/ros2/GeneratorLaunch.scala diff --git a/shared/src/main/scala/org/sireum/hamr/codegen/ros2/Generator.scala b/shared/src/main/scala/org/sireum/hamr/codegen/ros2/Generator.scala index b05d018c..787b8cfd 100644 --- a/shared/src/main/scala/org/sireum/hamr/codegen/ros2/Generator.scala +++ b/shared/src/main/scala/org/sireum/hamr/codegen/ros2/Generator.scala @@ -15,12 +15,6 @@ object Generator { val toolName: String = "Ros2Codegen" val node_executable_filename_suffix: String = "_exe" - val launch_node_decl_suffix: String = "_node" - val py_launch_file_name_suffix: String = ".launch.py" - val xml_launch_file_name_suffix: String = ".launch.xml" - val py_package_name_suffix: String = "_py_pkg" - val py_src_node_name_suffix: String = "_src.py" - val py_src_node_entry_point_name: String = "main" val cpp_package_name_suffix: String = "_cpp_pkg" val cpp_src_node_name_suffix: String = "_src.cpp" val cpp_src_node_header_name_suffix: String = "_src.hpp" @@ -33,37 +27,12 @@ object Generator { // Mutex is used for thread locking in C++ val mutex_name: String = "mutex_" - - def genPyLaunchFileName(compNameS: String): String = { - // create launch file name - val nodeNameT: String = s"${compNameS}${py_launch_file_name_suffix}" - return nodeNameT - } - - def genXmlLaunchFileName(compNameS: String): String = { - // create launch file name - val nodeNameT: String = s"${compNameS}${xml_launch_file_name_suffix}" - return nodeNameT - } - def genCppPackageName(packageNameS: String): String = { // create target package name val packageNameT: String = s"${packageNameS}${cpp_package_name_suffix}" return packageNameT } - def genPyPackageName(packageNameS: String): String = { - // create target package name - val packageNameT: String = s"${packageNameS}${py_package_name_suffix}" - return packageNameT - } - - def genPyNodeSourceName(compNameS: String): String = { - // create target node name - val nodeNameT: String = s"${compNameS}${py_src_node_name_suffix}" - return nodeNameT - } - def genCppNodeSourceName(compNameS: String): String = { // create node file name val nodeNameT: String = s"${compNameS}${cpp_src_node_name_suffix}" @@ -179,86 +148,6 @@ object Generator { return s"${prefix}${msg}" } - def seqToString(seq: ISZ[String], separator: String): String = { - var str = "" - for (s <- seq) { - str = s"$str$s$separator" - } - str = ops.StringOps(str).substring(0, str.size - 1) - return str - } - - //================================================ - // Setup file for node source package (Python) - // Example: https://github.com/santoslab/ros-examples/blob/main/tempControl_ws/src/tc_py_pkg/setup.py - //================================================ - - // genPySetupEntryPointDecl - generates entry point declaration - // (console scripts entry) in setup file - - // Example: - // "ts_exe = tc_py_pkg.ts_src:main" - def genPySetupEntryPointDecl(modelName: String, - componentName: String): ST = { - val node_source_file_nameT = genPyNodeSourceName(componentName) - val py_package_nameT = genPyPackageName(modelName) - val node_executable_file_nameT = genExecutableFileName(componentName) - val entryPointDecl:ST - = st"\"${node_executable_file_nameT} = ${py_package_nameT}.${node_source_file_nameT}:${py_src_node_entry_point_name}\"" - return entryPointDecl - } - - // Setup file for node source package - // Example: https://github.com/santoslab/ros-examples/blob/main/tempControl_ws/src/tc_py_pkg/setup.py - def genPySetupFile(modelName: String, threadComponents: ISZ[AadlThread]): (ISZ[String], ST) = { - val top_level_package_nameT: String = genPyPackageName(modelName) - val fileName: String = "setup.py" - - // build entry point declarations - var entry_point_decls: ISZ[ST] = IS() - for (comp <- threadComponents) { - val launch_node_decl_nameT = genPyFormatLaunchNodeDeclName(genNodeName(comp)) - entry_point_decls = - entry_point_decls :+ genPySetupEntryPointDecl(modelName, genNodeName(comp)) - } - - val setupFileBody = - st"""# ${fileName} in src/${top_level_package_nameT} - | - |from setuptools import find_packages, setup - | - |package_name = '${top_level_package_nameT}' - | - |setup( - | name=package_name, - | version='0.0.0', - | packages=find_packages(exclude=['test']), - | data_files=[ - | ('share/ament_index/resource_index/packages', - | ['resource/' + package_name]), - | ('share/' + package_name, ['package.xml']), - | ], - | install_requires=['setuptools'], - | zip_safe=True, - | maintainer='sireum', - | maintainer_email='sireum@todo.todo', - | description='TODO: Package description', - | license='TODO: License declaration', - | tests_require=['pytest'], - | entry_points={ - | 'console_scripts': [ - | ${(entry_point_decls, ",\n")} - | ], - | }, - |) - """ - - val filePath: ISZ[String] = IS("src", top_level_package_nameT, fileName) - - return (filePath, setupFileBody) - } - - //================================================ // Setup files for node source package (C++) // Example: https://github.com/santoslab/ros-examples/blob/main/tempControlcpp_ws/src/tc_cpp_pkg/CMakeLists.txt @@ -386,230 +275,6 @@ object Generator { return (filePath, setupFileBody, F, IS(Marker(startMarker, endMarker))) } - - //================================================ - // L a u n c h File Setup Files - //================================================ - - def genLaunchCMakeListsFile(modelName: String): (ISZ[String], ST, B, ISZ[Marker]) = { - val top_level_package_nameT: String = genCppPackageName(modelName) - val fileName: String = "CMakeLists.txt" - - val setupFileBody = - st"""cmake_minimum_required(VERSION 3.8) - |project(${top_level_package_nameT}_bringup) - | - |if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") - | add_compile_options(-Wall -Wextra -Wpedantic) - |endif() - | - |find_package(ament_cmake REQUIRED) - | - |install(DIRECTORY - | launch - | DESTINATION share/$${PROJECT_NAME} - |) - | - |ament_package() - """ - - val filePath: ISZ[String] = IS("src", s"${top_level_package_nameT}_bringup", fileName) - - return (filePath, setupFileBody, T, IS()) - } - - def genLaunchPackageFile(modelName: String): (ISZ[String], ST, B, ISZ[Marker]) = { - val top_level_package_nameT: String = genCppPackageName(modelName) - val fileName: String = "package.xml" - - val startMarker: String = "" - val endMarker: String = "" - - val setupFileBody = - st""" - | - | - | ${top_level_package_nameT}_bringup - | 0.0.0 - | TODO: Package description - | sireum - | TODO: License declaration - | - | ament_cmake - | - | ${top_level_package_nameT} - | - | ${startMarker} - | - | ${endMarker} - | - | ament_lint_auto - | ament_lint_common - | - | - | ament_cmake - | - | - """ - - val filePath: ISZ[String] = IS("src", s"${top_level_package_nameT}_bringup", fileName) - - return (filePath, setupFileBody, F, IS(Marker(startMarker, endMarker))) - } - - - //================================================ - // L a u n c h File (Python Format) - //================================================ - - // Example: - // tc_node = Node( ## Example is "tc_node" python variable name - // package="tc_cpp_pkg", - // executable="tc_exe" - // ) - def genPyFormatLaunchNodeDeclName(componentNameS: String): String = { - // create target launch node decl name - val launch_node_decl_nameT: String = s"${componentNameS}${launch_node_decl_suffix}" - return launch_node_decl_nameT - } - - // genLaunchNodeDecl() - generate node declaration - // Example: - // tc_node = Node( - // package="tc_cpp_pkg", - // executable="tc_exe" - // ) - def genPyFormatLaunchNodeDecl(launch_node_decl_nameT: String, - top_level_package_nameT: String, - component: AadlThread): ST = { - val node_executable_file_nameT = genExecutableFileName(genNodeName(component)) - val s = - st""" - |${launch_node_decl_nameT} = Node( - | package = ${top_level_package_nameT}, - | executable = ${node_executable_file_nameT} - | ) - """ - return s - } - - // Example: - // ld.add_action(tc_node) - def genPyFormatLaunchAddAction(launch_node_decl_nameT: String): ST = { - val s = st"""ld.add_action(${launch_node_decl_nameT})""" - return s - } - - // For example, see https://github.com/santoslab/ros-examples/blob/main/tempControl_ws/src/tc_bringup/launch/tc.launch.py - def genPyFormatLaunchFile(modelName: String, threadComponents: ISZ[AadlThread]): (ISZ[String], ST) = { - val fileName = genPyLaunchFileName(modelName) - - val top_level_package_nameT: String = genPyPackageName(modelName) - - var node_decls: ISZ[ST] = IS() - var ld_entries: ISZ[ST] = IS() - - for (comp <- threadComponents) { - val launch_node_decl_nameT = genPyFormatLaunchNodeDeclName(genNodeName(comp)) - node_decls = node_decls :+ genPyFormatLaunchNodeDecl(launch_node_decl_nameT, top_level_package_nameT, comp) - ld_entries = ld_entries :+ genPyFormatLaunchAddAction(launch_node_decl_nameT) - } - - val launchFileBody = - st"""from launch import LaunchDescription - |from launch_ros.actions import Node - | - |def generate_launch_description(): - | ld = LaunchDescription() - | - | ${(node_decls, "\n")} - | ${(ld_entries, "\n")} - | - | return ld - """ - - val filePath: ISZ[String] = IS("src", s"${modelName}_bringup", "launch", fileName) - - return (filePath, launchFileBody) - } - - //================================================ - // L a u n c h File (XML Format) - //================================================ - - // Generate node launch code - // Example: - // - def genXmlFormatLaunchNodeDecl(top_level_package_nameT: String, - thread: AadlThread): ST = { - val node_executable_file_nameT = genExecutableFileName(genNodeName(thread)) - val s = - st""" - | - | - """ - return s - } - - // Generate system launch code (including a system launch file) - // Example: - // - def genXmlFormatLaunchSystemDecl(top_level_package_nameT: String, - system: AadlSystem): ST = { - val launchFileName: String = genXmlLaunchFileName(system.identifier) - val s = - st""" - | - """ - return s - } - - def genXmlFormatLaunchDecls(component: AadlComponent, packageName: String): ISZ[ST] = { - var launch_decls: ISZ[ST] = IS() - - for (comp <- component.subComponents) { - comp match { - case thread: AadlThread => - launch_decls = launch_decls :+ genXmlFormatLaunchNodeDecl(packageName, thread) - case system: AadlSystem => - launch_decls = launch_decls :+ genXmlFormatLaunchSystemDecl(packageName, system) - case process: AadlProcess => - launch_decls = launch_decls ++ genXmlFormatLaunchDecls(process, packageName) - case _ => - } - } - - return launch_decls - } - - // For example, see https://github.com/santoslab/ros-examples/blob/main/tempControl_ws/src/tc_bringup/launch/tc.launch.py - // Creates a launch file for each system component in the model - def genXmlFormatLaunchFiles(modelName: String, threadComponents: ISZ[AadlThread], - systemComponents: ISZ[AadlSystem]): ISZ[(ISZ[String], ST, B, ISZ[Marker])] = { - val top_level_package_nameT: String = genCppPackageName(modelName) - - var launchFiles: ISZ[(ISZ[String], ST, B, ISZ[Marker])] = IS() - - for (system <- systemComponents) { - val fileName = genXmlLaunchFileName(system.identifier) - - val launch_decls: ISZ[ST] = genXmlFormatLaunchDecls(system, top_level_package_nameT) - - val launchFileBody = - st""" - | ${(launch_decls, "\n")} - | - """ - - val filePath: ISZ[String] = IS("src", s"${top_level_package_nameT}_bringup", "launch", fileName) - - launchFiles = launchFiles :+ (filePath, launchFileBody, T, IS()) - } - - return launchFiles - } - - //================================================ // I n t e r f a c e s Setup Files //================================================ @@ -2778,27 +2443,6 @@ object Generator { //================================================ // P a c k a g e G e n e r a t o r s //================================================ - - def genPyNodePkg(modelName: String, threadComponents: ISZ[AadlThread], connectionMap: Map[ISZ[String], ISZ[ISZ[String]]], - strictAADLMode: B): ISZ[(ISZ[String], ST)] = { - var files: ISZ[(ISZ[String], ST)] = IS() - - files = files :+ genPyFormatLaunchFile(modelName, threadComponents) - files = files :+ genPySetupFile(modelName, threadComponents) - - return files - } - - def genPyLaunchPkg(modelName: String, threadComponents: ISZ[AadlThread]): ISZ[(ISZ[String], ST, B, ISZ[Marker])] = { - var files: ISZ[(ISZ[String], ST, B, ISZ[Marker])] = IS() - - //files = files :+ genXmlFormatLaunchFile(modelName, threadComponents) - files = files :+ genLaunchCMakeListsFile(modelName) - files = files :+ genLaunchPackageFile(modelName) - - return files - } - def genCppNodePkg(modelName: String, threadComponents: ISZ[AadlThread], connectionMap: Map[ISZ[String], ISZ[ISZ[String]]], datatypeMap: Map[AadlType, (String, ISZ[String])], strictAADLMode: B, invertTopicBinding: B, reporter: Reporter): ISZ[(ISZ[String], ST, B, ISZ[Marker])] = { @@ -2817,16 +2461,6 @@ object Generator { return files } - def genXmlLaunchPkg(modelName: String, threadComponents: ISZ[AadlThread], systemComponents: ISZ[AadlSystem]): ISZ[(ISZ[String], ST, B, ISZ[Marker])] = { - var files: ISZ[(ISZ[String], ST, B, ISZ[Marker])] = IS() - - files = files ++ genXmlFormatLaunchFiles(modelName, threadComponents, systemComponents) - files = files :+ genLaunchCMakeListsFile(modelName) - files = files :+ genLaunchPackageFile(modelName) - - return files - } - // The same datatype package will work regardless of other packages' types // ROS2 data/message types are defined in a "{package_name}_interfaces" package according to convention def genInterfacesPkg(modelName: String, datatypeMap: Map[AadlType, (String, ISZ[String])]): ISZ[(ISZ[String], ST, B, ISZ[Marker])] = { diff --git a/shared/src/main/scala/org/sireum/hamr/codegen/ros2/GeneratorLaunch.scala b/shared/src/main/scala/org/sireum/hamr/codegen/ros2/GeneratorLaunch.scala new file mode 100644 index 00000000..91dd2e44 --- /dev/null +++ b/shared/src/main/scala/org/sireum/hamr/codegen/ros2/GeneratorLaunch.scala @@ -0,0 +1,327 @@ +// #Sireum + +package org.sireum.hamr.codegen.ros2 + +import org.sireum._ +import org.sireum.hamr.codegen.common.containers.Marker +import org.sireum.hamr.codegen.common.symbols.{AadlComponent, AadlProcess, AadlSystem, AadlThread} + +object GeneratorLaunch { + + val node_executable_filename_suffix: String = "_exe" + val launch_node_decl_suffix: String = "_node" + val py_package_name_suffix: String = "_py_pkg" + val cpp_package_name_suffix: String = "_cpp_pkg" + val py_launch_file_name_suffix: String = ".launch.py" + val xml_launch_file_name_suffix: String = ".launch.xml" + + def genCppPackageName(packageNameS: String): String = { + // create target package name + val packageNameT: String = s"${packageNameS}${cpp_package_name_suffix}" + return packageNameT + } + + def genPyPackageName(packageNameS: String): String = { + // create target package name + val packageNameT: String = s"${packageNameS}${py_package_name_suffix}" + return packageNameT + } + + def genLaunchPackageType(modelName: String, launchType: String): String = { + launchType match { + case "Cpp" => return genCppPackageName(modelName) + case "Python" => return genPyPackageName(modelName) + } + } + + //================================================ + // L a u n c h File Setup Files + //================================================ + def genLaunchCMakeListsFile(top_level_package_nameT: String): (ISZ[String], ST, B, ISZ[Marker]) = { + val fileName: String = "CMakeLists.txt" + + val setupFileBody = + st"""cmake_minimum_required(VERSION 3.8) + |project(${top_level_package_nameT}_bringup) + | + |if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + | add_compile_options(-Wall -Wextra -Wpedantic) + |endif() + | + |find_package(ament_cmake REQUIRED) + | + |install(DIRECTORY + | launch + | DESTINATION share/$${PROJECT_NAME} + |) + | + |ament_package() + """ + + val filePath: ISZ[String] = IS("src", s"${top_level_package_nameT}_bringup", fileName) + + return (filePath, setupFileBody, T, IS()) + } + + def genLaunchPackageFile(top_level_package_nameT: String): (ISZ[String], ST, B, ISZ[Marker]) = { + val fileName: String = "package.xml" + + val startMarker: String = "" + val endMarker: String = "" + + val setupFileBody = + st""" + | + | + | ${top_level_package_nameT}_bringup + | 0.0.0 + | TODO: Package description + | sireum + | TODO: License declaration + | + | ament_cmake + | + | ${top_level_package_nameT} + | + | ${startMarker} + | + | ${endMarker} + | + | ament_lint_auto + | ament_lint_common + | + | + | ament_cmake + | + | + """ + + val filePath: ISZ[String] = IS("src", s"${top_level_package_nameT}_bringup", fileName) + + return (filePath, setupFileBody, F, IS(Marker(startMarker, endMarker))) + } + + //================================================ + // L a u n c h XML Files + //================================================ + + def genExecutableFileName(componentNameS: String): String = { + // create target executable name + val executableFileNameT: String = s"${componentNameS}${node_executable_filename_suffix}" + return executableFileNameT + } + + def genNodeName(component: AadlThread): String = { + var name: ST = st"" + var i: Z = 1 + while (i < component.path.size) { + name = st"${name}_${component.path.apply(i)}" + i = i + 1 + } + return ops.StringOps(name.render).substring(1, name.render.size) + } + + def genXmlFormatLaunchNodeDecl(top_level_package_nameT: String, + thread: AadlThread): ST = { + val node_executable_file_nameT = genExecutableFileName(genNodeName(thread)) + val s = + st""" + | + | + """ + return s + } + + def genXmlLaunchFileName(compNameS: String): String = { + // create launch file name + val nodeNameT: String = s"${compNameS}${xml_launch_file_name_suffix}" + return nodeNameT + } + + def genXmlFormatLaunchSystemDecl(top_level_package_nameT: String, + system: AadlSystem): ST = { + val launchFileName: String = genXmlLaunchFileName(system.identifier) + val s = + st""" + | + """ + return s + } + + def genXmlFormatLaunchDecls(component: AadlComponent, packageName: String): ISZ[ST] = { + var launch_decls: ISZ[ST] = IS() + + for (comp <- component.subComponents) { + comp match { + case thread: AadlThread => + launch_decls = launch_decls :+ genXmlFormatLaunchNodeDecl(packageName, thread) + case system: AadlSystem => + launch_decls = launch_decls :+ genXmlFormatLaunchSystemDecl(packageName, system) + case process: AadlProcess => + launch_decls = launch_decls ++ genXmlFormatLaunchDecls(process, packageName) + case _ => + } + } + + return launch_decls + } + + def genXmlFormatLaunchFiles(modelName: String, threadComponents: ISZ[AadlThread], + systemComponents: ISZ[AadlSystem], top_level_package_nameT: String): ISZ[(ISZ[String], ST, B, ISZ[Marker])] = { + var launchFiles: ISZ[(ISZ[String], ST, B, ISZ[Marker])] = IS() + + for (system <- systemComponents) { + val fileName = genXmlLaunchFileName(system.identifier) + + val launch_decls: ISZ[ST] = genXmlFormatLaunchDecls(system, top_level_package_nameT) + + val launchFileBody = + st""" + | ${(launch_decls, "\n")} + | + """ + + val filePath: ISZ[String] = IS("src", s"${top_level_package_nameT}_bringup", "launch", fileName) + + launchFiles = launchFiles :+ (filePath, launchFileBody, T, IS()) + } + + return launchFiles + } + + def genXmlLaunchPkg(modelName: String, threadComponents: ISZ[AadlThread], systemComponents: ISZ[AadlSystem], launchType: String): ISZ[(ISZ[String], ST, B, ISZ[Marker])] = { + val top_level_package_nameT: String = genLaunchPackageType(modelName, launchType) + var files: ISZ[(ISZ[String], ST, B, ISZ[Marker])] = IS() + + files = files ++ genXmlFormatLaunchFiles(modelName, threadComponents, systemComponents, top_level_package_nameT) + files = files :+ genLaunchCMakeListsFile(top_level_package_nameT) + files = files :+ genLaunchPackageFile(top_level_package_nameT) + + return files + } + + //================================================ + // L a u n c h Python Files + //================================================ + def genPyLaunchFileName(compNameS: String): String = { + // create launch file name + val nodeNameT: String = s"${compNameS}${py_launch_file_name_suffix}" + return nodeNameT + } + + def genPyFormatLaunchNodeDeclName(componentNameS: String): String = { + // create target launch node decl name + val launch_node_decl_nameT: String = s"${componentNameS}${launch_node_decl_suffix}" + return launch_node_decl_nameT + } + + def genPyFormatLaunchNodeDecl(top_level_package_nameT: String, + thread: AadlThread): ST = { + val node_executable_file_nameT = genExecutableFileName(genNodeName(thread)) + val launch_node_decl_nameT = genPyFormatLaunchNodeDeclName(genNodeName(thread)) + val s = + st"""${launch_node_decl_nameT} = Node( + | package = "${top_level_package_nameT}", + | executable = "${node_executable_file_nameT}" + |) + """ + return s + } + + def genPyFormatLaunchSystemDecl(top_level_package_nameT: String, + system: AadlSystem): ST = { + val launch_node_decl_nameT = genPyFormatLaunchNodeDeclName(system.identifier) + val launchFileName: String = genPyLaunchFileName(system.identifier) + val s = + st"""${launch_node_decl_nameT} = IncludeLaunchDescription( + | PythonLaunchDescriptionSource( + | os.path.join(get_package_share_directory('${top_level_package_nameT}_bringup'), + | 'launch/${launchFileName}') + | ) + |) + """ + return s + } + + def genPyFormatLaunchDecls(component: AadlComponent, packageName: String): ISZ[ST] = { + var launch_decls: ISZ[ST] = IS() + + for (comp <- component.subComponents) { + comp match { + case thread: AadlThread => + launch_decls = launch_decls :+ genPyFormatLaunchNodeDecl(packageName, thread) + case system: AadlSystem => + launch_decls = launch_decls :+ genPyFormatLaunchSystemDecl(packageName, system) + case process: AadlProcess => + launch_decls = launch_decls ++ genPyFormatLaunchDecls(process, packageName) + case _ => + } + } + + return launch_decls + } + + def genPyFormatLaunchAddAction(component: AadlComponent): ISZ[ST] = { + var ld_entries: ISZ[ST] = IS() + + for(comp <- component.subComponents) { + comp match { + case thread: AadlThread => + ld_entries = ld_entries :+ st"""ld.add_action(${genPyFormatLaunchNodeDeclName(genNodeName(thread))})""" + case system: AadlSystem => + ld_entries = ld_entries :+ st"""ld.add_action(${genPyFormatLaunchNodeDeclName(system.identifier)})""" + case process: AadlProcess => + ld_entries = ld_entries ++ genPyFormatLaunchAddAction(process) + case _ => + } + } + + return ld_entries + } + + def genPyFormatLaunchFile(modelName: String, threadComponents: ISZ[AadlThread], + systemComponents: ISZ[AadlSystem], top_level_package_nameT: String): ISZ[(ISZ[String], ST, B, ISZ[Marker])] = { + var launchFiles: ISZ[(ISZ[String], ST, B, ISZ[Marker])] = IS() + + for (system <- systemComponents) { + val fileName = genPyLaunchFileName(system.identifier) + + val node_decls: ISZ[ST] = genPyFormatLaunchDecls(system, top_level_package_nameT) + val ld_entries: ISZ[ST] = genPyFormatLaunchAddAction(system) + + val launchFileBody = + st"""from launch import LaunchDescription + |from launch_ros.actions import Node + | + |import os + |from ament_index_python.packages import get_package_share_directory + |from launch.actions import IncludeLaunchDescription + |from launch.launch_description_sources import PythonLaunchDescriptionSource + | + |def generate_launch_description(): + | ld = LaunchDescription() + | + | ${(node_decls, "\n")} + | ${(ld_entries, "\n")} + | + | return ld + """ + + val filePath: ISZ[String] = IS("src", s"${top_level_package_nameT}_bringup", "launch", fileName) + + launchFiles = launchFiles :+ (filePath, launchFileBody, T, IS()) + } + + return launchFiles + } + + def genPyLaunchPkg(modelName: String, threadComponents: ISZ[AadlThread], systemComponents: ISZ[AadlSystem], nodeLanguage: String): ISZ[(ISZ[String], ST, B, ISZ[Marker])] = { + val top_level_package_nameT: String = genLaunchPackageType(modelName, nodeLanguage) + var files: ISZ[(ISZ[String], ST, B, ISZ[Marker])] = IS() + files = files :+ genLaunchCMakeListsFile(top_level_package_nameT) + files = files :+ genLaunchPackageFile(top_level_package_nameT) + files = files ++ genPyFormatLaunchFile(modelName, threadComponents, systemComponents, top_level_package_nameT) + + return files + } +} diff --git a/shared/src/main/scala/org/sireum/hamr/codegen/ros2/GeneratorPy.scala b/shared/src/main/scala/org/sireum/hamr/codegen/ros2/GeneratorPy.scala index a10032f7..b73f8a0d 100644 --- a/shared/src/main/scala/org/sireum/hamr/codegen/ros2/GeneratorPy.scala +++ b/shared/src/main/scala/org/sireum/hamr/codegen/ros2/GeneratorPy.scala @@ -15,8 +15,6 @@ object GeneratorPy { val toolName: String = "Ros2Codegen" val node_executable_filename_suffix: String = "_exe" - val launch_node_decl_suffix: String = "_node" - val py_launch_file_name_suffix: String = ".launch.py" val py_package_name_suffix: String = "_py_pkg" val py_src_node_name_suffix: String = "_src.py" val py_src_node_entry_point_name: String = "main" @@ -25,12 +23,6 @@ object GeneratorPy { val callback_group_type: String = "ReentrantCallbackGroup" val callback_group_name: String = "cb_group_" - def genPyLaunchFileName(compNameS: String): String = { - // create launch file name - val nodeNameT: String = s"${compNameS}${py_launch_file_name_suffix}" - return nodeNameT - } - def genPyPackageName(packageNameS: String): String = { // create target package name val packageNameT: String = s"${packageNameS}${py_package_name_suffix}" @@ -116,15 +108,6 @@ object GeneratorPy { return s } - def seqToString(seq: ISZ[String], separator: String): String = { - var str = "" - for (s <- seq) { - str = s"$str$s$separator" - } - //str = ops.StringOps(str).substring(0, str.size - 1) - return str - } - //================================================ // Setup file for node source package (Python) // Example: https://github.com/santoslab/ros-examples/blob/main/tempControl_ws/src/tc_py_pkg/setup.py @@ -412,208 +395,6 @@ object GeneratorPy { return (filePath, setupFileBody, T, IS()) } - //================================================ - // L a u n c h File Setup Files - //================================================ - - def genLaunchCMakeListsFile(modelName: String): (ISZ[String], ST, B, ISZ[Marker]) = { - val top_level_package_nameT: String = genPyPackageName(modelName) - val fileName: String = "CMakeLists.txt" - - val setupFileBody = - st"""cmake_minimum_required(VERSION 3.8) - |project(${top_level_package_nameT}_bringup) - | - |if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") - | add_compile_options(-Wall -Wextra -Wpedantic) - |endif() - | - |find_package(ament_cmake REQUIRED) - | - |install(DIRECTORY - | launch - | DESTINATION share/$${PROJECT_NAME} - |) - | - |ament_package() - """ - - val filePath: ISZ[String] = IS("src", s"${top_level_package_nameT}_bringup", fileName) - - return (filePath, setupFileBody, T, IS()) - } - - def genLaunchPackageFile(modelName: String): (ISZ[String], ST, B, ISZ[Marker]) = { - val top_level_package_nameT: String = genPyPackageName(modelName) - val fileName: String = "package.xml" - - val startMarker: String = "" - val endMarker: String = "" - - val setupFileBody = - st""" - | - | - | ${top_level_package_nameT}_bringup - | 0.0.0 - | TODO: Package description - | sireum - | TODO: License declaration - | - | ament_cmake - | - | ${top_level_package_nameT} - | ${top_level_package_nameT}_interfaces - | - | ${startMarker} - | - | ${endMarker} - | - | ament_lint_auto - | ament_lint_common - | - | - | ament_cmake - | - | - """ - - val filePath: ISZ[String] = IS("src", s"${top_level_package_nameT}_bringup", fileName) - - return (filePath, setupFileBody, F, IS(Marker(startMarker, endMarker))) - } - - - //================================================ - // L a u n c h File (Python Format) - //================================================ - - // Example: - // tc_node = Node( ## Example is "tc_node" python variable name - // package="tc_cpp_pkg", - // executable="tc_exe" - // ) - def genPyFormatLaunchNodeDeclName(componentNameS: String): String = { - // create target launch node decl name - val launch_node_decl_nameT: String = s"${componentNameS}${launch_node_decl_suffix}" - return launch_node_decl_nameT - } - - // genLaunchNodeDecl() - generate node declaration - // Example: - // tc_node = Node( - // package="tc_cpp_pkg", - // executable="tc_exe" - // ) - def genPyFormatLaunchNodeDecl(top_level_package_nameT: String, - thread: AadlThread): ST = { - val node_executable_file_nameT = genExecutableFileName(genNodeName(thread)) - val launch_node_decl_nameT = genPyFormatLaunchNodeDeclName(genNodeName(thread)) - val s = - st"""${launch_node_decl_nameT} = Node( - | package = "${top_level_package_nameT}", - | executable = "${node_executable_file_nameT}" - |) - """ - return s - } - - // Generate system launch code (including a system launch file) - // Example: - // - def genPyFormatLaunchSystemDecl(top_level_package_nameT: String, - system: AadlSystem): ST = { - val launch_node_decl_nameT = genPyFormatLaunchNodeDeclName(system.identifier) - val launchFileName: String = genPyLaunchFileName(system.identifier) - val s = - st"""${launch_node_decl_nameT} = IncludeLaunchDescription( - | PythonLaunchDescriptionSource( - | os.path.join(get_package_share_directory('${top_level_package_nameT}_bringup'), - | 'launch/${launchFileName}') - | ) - |) - """ - return s - } - - def genPyFormatLaunchDecls(component: AadlComponent, packageName: String): ISZ[ST] = { - var launch_decls: ISZ[ST] = IS() - - for (comp <- component.subComponents) { - comp match { - case thread: AadlThread => - launch_decls = launch_decls :+ genPyFormatLaunchNodeDecl(packageName, thread) - case system: AadlSystem => - launch_decls = launch_decls :+ genPyFormatLaunchSystemDecl(packageName, system) - case process: AadlProcess => - launch_decls = launch_decls ++ genPyFormatLaunchDecls(process, packageName) - case _ => - } - } - - return launch_decls - } - - // Example: - // ld.add_action(tc_node) - def genPyFormatLaunchAddAction(component: AadlComponent): ISZ[ST] = { - var ld_entries: ISZ[ST] = IS() - - for(comp <- component.subComponents) { - comp match { - case thread: AadlThread => - ld_entries = ld_entries :+ st"""ld.add_action(${genPyFormatLaunchNodeDeclName(genNodeName(thread))})""" - case system: AadlSystem => - ld_entries = ld_entries :+ st"""ld.add_action(${genPyFormatLaunchNodeDeclName(system.identifier)})""" - case process: AadlProcess => - ld_entries = ld_entries ++ genPyFormatLaunchAddAction(process) - case _ => - } - } - - return ld_entries - } - - // For example, see https://github.com/santoslab/ros-examples/blob/main/tempControl_ws/src/tc_bringup/launch/tc.launch.py - def genPyFormatLaunchFile(modelName: String, threadComponents: ISZ[AadlThread], - systemComponents: ISZ[AadlSystem]): ISZ[(ISZ[String], ST, B, ISZ[Marker])] = { - - val top_level_package_nameT: String = genPyPackageName(modelName) - - var launchFiles: ISZ[(ISZ[String], ST, B, ISZ[Marker])] = IS() - - for (system <- systemComponents) { - val fileName = genPyLaunchFileName(system.identifier) - - val node_decls: ISZ[ST] = genPyFormatLaunchDecls(system, top_level_package_nameT) - val ld_entries: ISZ[ST] = genPyFormatLaunchAddAction(system) - - val launchFileBody = - st"""from launch import LaunchDescription - |from launch_ros.actions import Node - | - |import os - |from ament_index_python.packages import get_package_share_directory - |from launch.actions import IncludeLaunchDescription - |from launch.launch_description_sources import PythonLaunchDescriptionSource - | - |def generate_launch_description(): - | ld = LaunchDescription() - | - | ${(node_decls, "\n")} - | ${(ld_entries, "\n")} - | - | return ld - """ - - val filePath: ISZ[String] = IS("src", s"${top_level_package_nameT}_bringup", "launch", fileName) - - launchFiles = launchFiles :+ (filePath, launchFileBody, T, IS()) - } - - return launchFiles - } - //================================================ // I n t e r f a c e s Setup Files //================================================ @@ -1934,15 +1715,6 @@ object GeneratorPy { return files } - def genPyLaunchPkg(modelName: String, threadComponents: ISZ[AadlThread], systemComponents: ISZ[AadlSystem]): ISZ[(ISZ[String], ST, B, ISZ[Marker])] = { - var files: ISZ[(ISZ[String], ST, B, ISZ[Marker])] = IS() - files = files :+ genLaunchCMakeListsFile(modelName) - files = files :+ genLaunchPackageFile(modelName) - files = files ++ genPyFormatLaunchFile(modelName, threadComponents, systemComponents) - - return files - } - // The same datatype package will work regardless of other packages' types // ROS2 data/message types are defined in a "{package_name}_interfaces" package according to convention def genInterfacesPkg(modelName: String, datatypeMap: Map[AadlType, (String, ISZ[String])]): ISZ[(ISZ[String], ST, B, ISZ[Marker])] = { diff --git a/shared/src/main/scala/org/sireum/hamr/codegen/ros2/Ros2Codegen.scala b/shared/src/main/scala/org/sireum/hamr/codegen/ros2/Ros2Codegen.scala index f2eb65fb..832794be 100644 --- a/shared/src/main/scala/org/sireum/hamr/codegen/ros2/Ros2Codegen.scala +++ b/shared/src/main/scala/org/sireum/hamr/codegen/ros2/Ros2Codegen.scala @@ -49,21 +49,17 @@ object Ros2Codegen { case "Cpp" => files = Generator.genCppNodePkg(modelName, threadComponents, connectionMap, datatypeMap, options.strictAadlMode, options.invertTopicBinding, reporter) - case "Python" => files = GeneratorPy.genPyNodePkg(modelName, threadComponents, connectionMap, datatypeMap, options.strictAadlMode, + files = files ++ Generator.genInterfacesPkg(modelName, datatypeMap) + case "Python" => + files = GeneratorPy.genPyNodePkg(modelName, threadComponents, connectionMap, datatypeMap, options.strictAadlMode, options.invertTopicBinding, reporter) + files = files ++ GeneratorPy.genInterfacesPkg(modelName, datatypeMap) case _ => reporter.error(None(), toolName, s"Unknown code type: ${options.ros2NodesLanguage.name}") } options.ros2LaunchLanguage.name match { - case "Xml" => files = files ++ Generator.genXmlLaunchPkg(modelName, threadComponents, systemComponents) - case "Python" => files = files ++ GeneratorPy.genPyLaunchPkg(modelName, threadComponents, systemComponents) - case _ => reporter.error(None(), toolName, s"Unknown code type: ${options.ros2NodesLanguage.name}") - } - - //files = files ++ Generator.genInterfacesPkg(modelName, datatypeMap) - options.ros2NodesLanguage.name match { - case "Cpp" => files = files ++ Generator.genInterfacesPkg(modelName, datatypeMap) - case "Python" => files = files ++ GeneratorPy.genInterfacesPkg(modelName, datatypeMap) + case "Xml" => files = files ++ GeneratorLaunch.genXmlLaunchPkg(modelName, threadComponents, systemComponents, options.ros2NodesLanguage.name) + case "Python" => files = files ++ GeneratorLaunch.genPyLaunchPkg(modelName, threadComponents, systemComponents, options.ros2NodesLanguage.name) case _ => reporter.error(None(), toolName, s"Unknown code type: ${options.ros2NodesLanguage.name}") } From 40e30b4a904ea040fa153611147ab8a06a927814 Mon Sep 17 00:00:00 2001 From: CatLiSantos Date: Wed, 7 May 2025 15:25:20 -0500 Subject: [PATCH 15/18] Added an interfaces generator file --- .../sireum/hamr/codegen/ros2/Generator.scala | 107 -------------- .../codegen/ros2/GeneratorInterfaces.scala | 136 ++++++++++++++++++ .../hamr/codegen/ros2/GeneratorPy.scala | 106 -------------- .../hamr/codegen/ros2/Ros2Codegen.scala | 4 +- 4 files changed, 138 insertions(+), 215 deletions(-) create mode 100644 shared/src/main/scala/org/sireum/hamr/codegen/ros2/GeneratorInterfaces.scala diff --git a/shared/src/main/scala/org/sireum/hamr/codegen/ros2/Generator.scala b/shared/src/main/scala/org/sireum/hamr/codegen/ros2/Generator.scala index 787b8cfd..fe3738aa 100644 --- a/shared/src/main/scala/org/sireum/hamr/codegen/ros2/Generator.scala +++ b/shared/src/main/scala/org/sireum/hamr/codegen/ros2/Generator.scala @@ -275,101 +275,6 @@ object Generator { return (filePath, setupFileBody, F, IS(Marker(startMarker, endMarker))) } - //================================================ - // I n t e r f a c e s Setup Files - //================================================ - // ROS2 data/message types are defined in a "{package_name}_interfaces" package according to convention - // The "Empty" datatype, which has no data fields, is used for event ports - - def genMsgFiles(modelName: String, datatypeMap: Map[AadlType, (String, ISZ[String])]): ISZ[(ISZ[String], ST, B, ISZ[Marker])] = { - var msg_files: ISZ[(ISZ[String], ST, B, ISZ[Marker])] = IS() - for (datatype <- datatypeMap.entries) { - msg_files = msg_files :+ genMsgFile(modelName, datatype._2._1, datatype._2._2) - } - msg_files = msg_files :+ (ISZ("src", s"${genCppPackageName(modelName)}_interfaces", "msg", "Empty.msg"), st"", T, IS()) - return msg_files - } - - def genMsgFile(modelName: String, datatypeName: String, datatypeContent: ISZ[String]): (ISZ[String], ST, B, ISZ[Marker]) = { - val top_level_package_nameT: String = genCppPackageName(modelName) - - val fileBody = st"${(datatypeContent, "\n")}" - - val filePath: ISZ[String] = IS("src", s"${top_level_package_nameT}_interfaces", "msg", s"${datatypeName}.msg") - - return (filePath, fileBody, T, IS()) - } - - def genInterfacesCMakeListsFile(modelName: String, datatypeMap: Map[AadlType, (String, ISZ[String])]): (ISZ[String], ST, B, ISZ[Marker]) = { - val top_level_package_nameT: String = genCppPackageName(modelName) - val fileName: String = "CMakeLists.txt" - var msgTypes: ISZ[String] = IS() - for (msg <- datatypeMap.valueSet.elements) { - msgTypes = msgTypes :+ s"msg/${msg._1}.msg" - } - msgTypes = msgTypes :+ s"msg/Empty.msg" - - val setupFileBody = - st"""cmake_minimum_required(VERSION 3.8) - |project(${top_level_package_nameT}_interfaces) - | - |if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") - | add_compile_options(-Wall -Wextra -Wpedantic) - |endif() - | - |find_package(ament_cmake REQUIRED) - | - |find_package(rosidl_default_generators REQUIRED) - | - |rosidl_generate_interfaces($${PROJECT_NAME} - | ${(msgTypes, "\n")} - |) - | - |ament_export_dependencies(rosidl_default_runtime) - | - |ament_package() - """ - - val filePath: ISZ[String] = IS("src", s"${top_level_package_nameT}_interfaces", fileName) - - return (filePath, setupFileBody, T, IS()) - } - - def genInterfacesPackageFile(modelName: String): (ISZ[String], ST, B, ISZ[Marker]) = { - val top_level_package_nameT: String = genCppPackageName(modelName) - val fileName: String = "package.xml" - - val setupFileBody = - st""" - | - | - | ${top_level_package_nameT}_interfaces - | 0.0.0 - | TODO: Package description - | sireum - | TODO: License declaration - | - | ament_cmake - | - | rosidl_default_generators - | rosidl_default_runtime - | rosidl_interface_packages - | - | ament_lint_auto - | ament_lint_common - | - | - | ament_cmake - | - | - """ - - val filePath: ISZ[String] = IS("src", s"${top_level_package_nameT}_interfaces", fileName) - - return (filePath, setupFileBody, T, IS()) - } - - //================================================ // Node files (C++) // Example: https://github.com/santoslab/ros-examples/tree/main/tempControlcpp_ws/src/tc_cpp_pkg/src @@ -2460,16 +2365,4 @@ object Generator { return files } - - // The same datatype package will work regardless of other packages' types - // ROS2 data/message types are defined in a "{package_name}_interfaces" package according to convention - def genInterfacesPkg(modelName: String, datatypeMap: Map[AadlType, (String, ISZ[String])]): ISZ[(ISZ[String], ST, B, ISZ[Marker])] = { - var files: ISZ[(ISZ[String], ST, B, ISZ[Marker])] = IS() - - files = files ++ genMsgFiles(modelName, datatypeMap) - files = files :+ genInterfacesCMakeListsFile(modelName, datatypeMap) - files = files :+ genInterfacesPackageFile(modelName) - - return files - } } diff --git a/shared/src/main/scala/org/sireum/hamr/codegen/ros2/GeneratorInterfaces.scala b/shared/src/main/scala/org/sireum/hamr/codegen/ros2/GeneratorInterfaces.scala new file mode 100644 index 00000000..ab0f394f --- /dev/null +++ b/shared/src/main/scala/org/sireum/hamr/codegen/ros2/GeneratorInterfaces.scala @@ -0,0 +1,136 @@ +// #Sireum + +package org.sireum.hamr.codegen.ros2 + +import org.sireum._ +import org.sireum.hamr.codegen.common.containers.Marker +import org.sireum.hamr.codegen.common.types.AadlType + +object GeneratorInterfaces { + + val py_package_name_suffix: String = "_py_pkg" + val cpp_package_name_suffix: String = "_cpp_pkg" + + def genCppPackageName(packageNameS: String): String = { + // create target package name + val packageNameT: String = s"${packageNameS}${cpp_package_name_suffix}" + return packageNameT + } + + def genPyPackageName(packageNameS: String): String = { + // create target package name + val packageNameT: String = s"${packageNameS}${py_package_name_suffix}" + return packageNameT + } + + def genLaunchPackageType(modelName: String, launchType: String): String = { + launchType match { + case "Cpp" => return genCppPackageName(modelName) + case "Python" => return genPyPackageName(modelName) + } + } + + //================================================ + // I n t e r f a c e s Setup Files + //================================================ + // ROS2 data/message types are defined in a "{package_name}_interfaces" package according to convention + // The "Empty" datatype, which has no data fields, is used for event ports + + def genMsgFile(modelName: String, datatypeName: String, datatypeContent: ISZ[String], top_level_package_nameT: String): (ISZ[String], ST, B, ISZ[Marker]) = { + + val fileBody = st"${(datatypeContent, "\n")}" + + val filePath: ISZ[String] = IS("src", s"${top_level_package_nameT}_interfaces", "msg", s"${datatypeName}.msg") + + return (filePath, fileBody, T, IS()) + } + + def genMsgFiles(modelName: String, datatypeMap: Map[AadlType, (String, ISZ[String])], top_level_package_nameT: String): ISZ[(ISZ[String], ST, B, ISZ[Marker])] = { + var msg_files: ISZ[(ISZ[String], ST, B, ISZ[Marker])] = IS() + for (datatype <- datatypeMap.entries) { + msg_files = msg_files :+ genMsgFile(modelName, datatype._2._1, datatype._2._2, top_level_package_nameT) + } + msg_files = msg_files :+ (ISZ("src", s"${top_level_package_nameT}_interfaces", "msg", "Empty.msg"), st"", T, IS()) + return msg_files + } + + def genInterfacesCMakeListsFile(modelName: String, datatypeMap: Map[AadlType, (String, ISZ[String])], top_level_package_nameT: String): (ISZ[String], ST, B, ISZ[Marker]) = { + val fileName: String = "CMakeLists.txt" + var msgTypes: ISZ[String] = IS() + for (msg <- datatypeMap.valueSet.elements) { + msgTypes = msgTypes :+ s"msg/${msg._1}.msg" + } + msgTypes = msgTypes :+ s"msg/Empty.msg" + + val setupFileBody = + st"""cmake_minimum_required(VERSION 3.8) + |project(${top_level_package_nameT}_interfaces) + | + |if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + | add_compile_options(-Wall -Wextra -Wpedantic) + |endif() + | + |find_package(ament_cmake REQUIRED) + | + |find_package(rosidl_default_generators REQUIRED) + | + |rosidl_generate_interfaces($${PROJECT_NAME} + | ${(msgTypes, "\n")} + |) + | + |ament_export_dependencies(rosidl_default_runtime) + | + |ament_package() + """ + + val filePath: ISZ[String] = IS("src", s"${top_level_package_nameT}_interfaces", fileName) + + return (filePath, setupFileBody, T, IS()) + } + + def genInterfacesPackageFile(modelName: String, top_level_package_nameT: String): (ISZ[String], ST, B, ISZ[Marker]) = { + val fileName: String = "package.xml" + + val setupFileBody = + st""" + | + | + | ${top_level_package_nameT}_interfaces + | 0.0.0 + | TODO: Package description + | sireum + | TODO: License declaration + | + | ament_cmake + | + | rosidl_default_generators + | rosidl_default_runtime + | rosidl_interface_packages + | + | ament_lint_auto + | ament_lint_common + | + | + | ament_cmake + | + | + """ + + val filePath: ISZ[String] = IS("src", s"${top_level_package_nameT}_interfaces", fileName) + + return (filePath, setupFileBody, T, IS()) + } + + // The same datatype package will work regardless of other packages' types + // ROS2 data/message types are defined in a "{package_name}_interfaces" package according to convention + def genInterfacesPkg(modelName: String, datatypeMap: Map[AadlType, (String, ISZ[String])], nodeLanguage: String): ISZ[(ISZ[String], ST, B, ISZ[Marker])] = { + val top_level_package_nameT: String = genLaunchPackageType(modelName, nodeLanguage) + var files: ISZ[(ISZ[String], ST, B, ISZ[Marker])] = IS() + + files = files ++ genMsgFiles(modelName, datatypeMap, top_level_package_nameT) + files = files :+ genInterfacesCMakeListsFile(modelName, datatypeMap, top_level_package_nameT) + files = files :+ genInterfacesPackageFile(modelName, top_level_package_nameT) + + return files + } +} diff --git a/shared/src/main/scala/org/sireum/hamr/codegen/ros2/GeneratorPy.scala b/shared/src/main/scala/org/sireum/hamr/codegen/ros2/GeneratorPy.scala index b73f8a0d..b554187a 100644 --- a/shared/src/main/scala/org/sireum/hamr/codegen/ros2/GeneratorPy.scala +++ b/shared/src/main/scala/org/sireum/hamr/codegen/ros2/GeneratorPy.scala @@ -395,100 +395,6 @@ object GeneratorPy { return (filePath, setupFileBody, T, IS()) } - //================================================ - // I n t e r f a c e s Setup Files - //================================================ - // ROS2 data/message types are defined in a "{package_name}_interfaces" package according to convention - // The "Empty" datatype, which has no data fields, is used for event ports - - def genMsgFiles(modelName: String, datatypeMap: Map[AadlType, (String, ISZ[String])]): ISZ[(ISZ[String], ST, B, ISZ[Marker])] = { - var msg_files: ISZ[(ISZ[String], ST, B, ISZ[Marker])] = IS() - for (datatype <- datatypeMap.entries) { - msg_files = msg_files :+ genMsgFile(modelName, datatype._2._1, datatype._2._2) - } - msg_files = msg_files :+ (ISZ("src", s"${genPyPackageName(modelName)}_interfaces", "msg", "Empty.msg"), st"", T, IS()) - return msg_files - } - - def genMsgFile(modelName: String, datatypeName: String, datatypeContent: ISZ[String]): (ISZ[String], ST, B, ISZ[Marker]) = { - val top_level_package_nameT: String = genPyPackageName(modelName) - - val fileBody = st"${(datatypeContent, "\n")}" - - val filePath: ISZ[String] = IS("src", s"${top_level_package_nameT}_interfaces", "msg", s"${datatypeName}.msg") - - return (filePath, fileBody, T, IS()) - } - - def genInterfacesCMakeListsFile(modelName: String, datatypeMap: Map[AadlType, (String, ISZ[String])]): (ISZ[String], ST, B, ISZ[Marker]) = { - val top_level_package_nameT: String = genPyPackageName(modelName) - val fileName: String = "CMakeLists.txt" - var msgTypes: ISZ[String] = IS() - for (msg <- datatypeMap.valueSet.elements) { - msgTypes = msgTypes :+ s"msg/${msg._1}.msg" - } - msgTypes = msgTypes :+ s"msg/Empty.msg" - - val setupFileBody = - st"""cmake_minimum_required(VERSION 3.8) - |project(${top_level_package_nameT}_interfaces) - | - |if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") - | add_compile_options(-Wall -Wextra -Wpedantic) - |endif() - | - |find_package(ament_cmake REQUIRED) - | - |find_package(rosidl_default_generators REQUIRED) - | - |rosidl_generate_interfaces($${PROJECT_NAME} - | ${(msgTypes, "\n")} - |) - | - |ament_export_dependencies(rosidl_default_runtime) - | - |ament_package() - """ - - val filePath: ISZ[String] = IS("src", s"${top_level_package_nameT}_interfaces", fileName) - - return (filePath, setupFileBody, T, IS()) - } - - def genInterfacesPackageFile(modelName: String): (ISZ[String], ST, B, ISZ[Marker]) = { - val top_level_package_nameT: String = genPyPackageName(modelName) - val fileName: String = "package.xml" - - val setupFileBody = - st""" - | - | - | ${top_level_package_nameT}_interfaces - | 0.0.0 - | TODO: Package description - | sireum - | TODO: License declaration - | - | ament_cmake - | - | rosidl_default_generators - | rosidl_default_runtime - | rosidl_interface_packages - | - | ament_lint_auto - | ament_lint_common - | - | - | ament_cmake - | - | - """ - - val filePath: ISZ[String] = IS("src", s"${top_level_package_nameT}_interfaces", fileName) - - return (filePath, setupFileBody, T, IS()) - } - //================================================ // Node files (Py) // Example: https://github.com/santoslab/ros-examples/tree/main/tempControl_ws/src/tc_py_pkg/tc_py_pkg @@ -1714,16 +1620,4 @@ object GeneratorPy { return files } - - // The same datatype package will work regardless of other packages' types - // ROS2 data/message types are defined in a "{package_name}_interfaces" package according to convention - def genInterfacesPkg(modelName: String, datatypeMap: Map[AadlType, (String, ISZ[String])]): ISZ[(ISZ[String], ST, B, ISZ[Marker])] = { - var files: ISZ[(ISZ[String], ST, B, ISZ[Marker])] = IS() - - files = files ++ genMsgFiles(modelName, datatypeMap) - files = files :+ genInterfacesCMakeListsFile(modelName, datatypeMap) - files = files :+ genInterfacesPackageFile(modelName) - - return files - } } diff --git a/shared/src/main/scala/org/sireum/hamr/codegen/ros2/Ros2Codegen.scala b/shared/src/main/scala/org/sireum/hamr/codegen/ros2/Ros2Codegen.scala index 832794be..123537f7 100644 --- a/shared/src/main/scala/org/sireum/hamr/codegen/ros2/Ros2Codegen.scala +++ b/shared/src/main/scala/org/sireum/hamr/codegen/ros2/Ros2Codegen.scala @@ -49,11 +49,9 @@ object Ros2Codegen { case "Cpp" => files = Generator.genCppNodePkg(modelName, threadComponents, connectionMap, datatypeMap, options.strictAadlMode, options.invertTopicBinding, reporter) - files = files ++ Generator.genInterfacesPkg(modelName, datatypeMap) case "Python" => files = GeneratorPy.genPyNodePkg(modelName, threadComponents, connectionMap, datatypeMap, options.strictAadlMode, options.invertTopicBinding, reporter) - files = files ++ GeneratorPy.genInterfacesPkg(modelName, datatypeMap) case _ => reporter.error(None(), toolName, s"Unknown code type: ${options.ros2NodesLanguage.name}") } @@ -63,6 +61,8 @@ object Ros2Codegen { case _ => reporter.error(None(), toolName, s"Unknown code type: ${options.ros2NodesLanguage.name}") } + files = files ++ GeneratorInterfaces.genInterfacesPkg(modelName, datatypeMap, options.ros2NodesLanguage.name) + for (file <- files) { var filePath: String = "" for (s <- file._1) { From 279a83cfe357b9d67817726013374bfd17ca289d Mon Sep 17 00:00:00 2001 From: CatLiSantos Date: Thu, 24 Jul 2025 17:43:16 -0500 Subject: [PATCH 16/18] Added example code generation and refactored --- .DS_Store | Bin 6148 -> 6148 bytes jvm/.DS_Store | Bin 6148 -> 6148 bytes jvm/src/.DS_Store | Bin 6148 -> 6148 bytes .../hamr/codegen/ros2/GeneratorPy.scala | 528 +++++++++++++----- 4 files changed, 380 insertions(+), 148 deletions(-) diff --git a/.DS_Store b/.DS_Store index 72b00e533993cb9ed35aeaadec9160cf3762f803..edc6312b638bf5c6e7057b82544d4b19cad78450 100644 GIT binary patch delta 21 ccmZoMXffE3!Ny^1XrZHEY;3f-fNhE(07In(-2eap delta 21 ccmZoMXffE3!Ny@?V6LNJY-G5(fNhE(07H%i+5i9m diff --git a/jvm/.DS_Store b/jvm/.DS_Store index 573962ced56fca9bd889b03c44132f48b6e6fec1..4d27c2c3c061307320ed6f05df791ea5bad9d8e5 100644 GIT binary patch delta 21 ccmZoMXffEZhl#`3&_YMS*w|?E5he#w07*#(EdT%j delta 21 ccmZoMXffEZhl#_)z+6Yc*vN465he#w07)_iDgXcg diff --git a/jvm/src/.DS_Store b/jvm/src/.DS_Store index 0fa0b30808a9b27c073a4aaf67769b8314d18686..354c21c28457880336f72977e123aeff74ad9898 100644 GIT binary patch delta 47 zcmZoMXffE}%`!QPwUooi%uGkY$iiasbymm89IVGVj14Vx6pW3HHeYAeXWGop@s}R} DNg)l8 delta 107 zcmZoMXffE}&BC~SvJXq4n|O7#xuvO&f~7&NjzYDik%5kaiLqI2EhmSlvc7dte0EN5 rUVbM77%(zIXa-&=4Wqgyv#_4wFflOKQ7|?#+TODO: License declaration | | rclpy + | rosidl_runtime_py | ${(pkgDependencies, "\n")} | | ${startMarker} @@ -242,7 +243,7 @@ object GeneratorPy { | ament_python | | - """ + """ val filePath: ISZ[String] = IS("src", top_level_package_nameT, fileName) @@ -316,7 +317,7 @@ object GeneratorPy { |def test_copyright(): | rc = main(argv=['.', 'test']) | assert rc == 0, 'Found errors' - """ + """ val filePath: ISZ[String] = IS("src", top_level_package_nameT, "test", fileName) @@ -353,7 +354,7 @@ object GeneratorPy { | assert rc == 0, \\ | 'Found %d code style errors / warnings:\n' % len(errors) + \\ | '\n'.join(errors) - """ + """ val filePath: ISZ[String] = IS("src", top_level_package_nameT, "test", fileName) @@ -388,7 +389,7 @@ object GeneratorPy { |def test_pep257(): | rc = main(argv=['.', 'test']) | assert rc == 0, 'Found code style errors / warnings' - """ + """ val filePath: ISZ[String] = IS("src", top_level_package_nameT, "test", fileName) @@ -400,6 +401,16 @@ object GeneratorPy { // Example: https://github.com/santoslab/ros-examples/tree/main/tempControl_ws/src/tc_py_pkg/tc_py_pkg //================================================ + def genPyDataPortInitializerStrict(inDataPort: AadlPort, nodeName: String, portType: String): ST = { + val portName = inDataPort.identifier + + val initializer: ST = + st"""def init_${portName}(self, val): + | self.enqueue(self.infrastructureIn_${portName}, val) + """ + return initializer + } + // Example: // self.subscription = self.create_subscription( // String, @@ -455,8 +466,8 @@ object GeneratorPy { st"""def ${handlerName}_thread(self): | with self.lock_: | self.receiveInputs(self.infrastructureIn_${handlerName}, self.applicationIn_${handlerName}) - | if self.applicationIn_${handlerName}.empty(): return - | self.handle_${handlerName}_base(self.applicationIn_${handlerName}.front()) + | if len(self.applicationIn_${handlerName}) == 0: return + | self.handle_${handlerName}_base(self.applicationIn_${handlerName}[0]) | self.applicationIn_${handlerName}.pop() | self.sendOutputs() """ @@ -468,18 +479,16 @@ object GeneratorPy { val handlerName = inPort.identifier val handler: ST = - if (!isSporadic || inPort.isInstanceOf[AadlDataPort]) st"self.enqueue(infrastructureIn_${handlerName}, msg)" + if (!isSporadic || inPort.isInstanceOf[AadlDataPort]) st"self.enqueue(self.infrastructureIn_${handlerName}, msg)" else st"""self.enqueue(self.infrastructureIn_${handlerName}, msg) - |thread = threading.Thread(target=${handlerName}_thread) + |thread = threading.Thread(target=self.${handlerName}_thread) |thread.daemon = True |thread.start() """ val method: ST = st"""def accept_${handlerName}(self, msg): - | typedMsg = ${portType}() - | typedMsg.data = msg | ${handler} """ return method @@ -489,7 +498,7 @@ object GeneratorPy { val portName = inPort.identifier val inMsgQueue: ST = - st"self.infrastructureIn_${portName} = Queue()" + st"self.infrastructureIn_${portName} = deque()" return inMsgQueue } @@ -497,19 +506,18 @@ object GeneratorPy { val portName = inPort.identifier val inMsgQueue: ST = - st"self.applicationIn_${portName} = Queue()" + st"self.applicationIn_${portName} = deque()" return inMsgQueue } def genPyGetApplicationInValue(inPort: AadlPort, portType: String): ST = { val portName = inPort.identifier - // Int32 is a placeholder message value val subscriptionMessageHeader: ST = st"""def get_${portName}(self): - | msg = applicationIn_${portName}.front() - | return get(msg) - |""" + | msg = self.applicationIn_${portName}[0] + | return msg + """ return subscriptionMessageHeader } @@ -521,12 +529,12 @@ object GeneratorPy { handlerCode = st"""def handle_${handlerName}(self): | raise NotImplementedError("Subclasses must implement this method") - |""" + """ } else { handlerCode = st"""def handle_${handlerName}(self, msg): | raise NotImplementedError("Subclasses must implement this method") - |""" + """ } return handlerCode @@ -540,18 +548,16 @@ object GeneratorPy { handlerCode = st"""def handle_${handlerName}_base(self, msg): | self.handle_${handlerName}() - |""" + """ } else { handlerCode = st"""def handle_${handlerName}_base(self, msg): | if type(msg) is ${portType}: - | typedMsg = ${portType}() - | typedMsg.data = msg - | self.handle_${handlerName}(typedMsg) + | self.handle_${handlerName}(msg) | else: | self.get_logger.error("Receiving wrong type of variable on port ${handlerName}.\nThis shouldn't be possible. If you are seeing this message, please notify this tool's current maintainer.") - |""" + """ } return handlerCode } @@ -568,13 +574,12 @@ object GeneratorPy { if (inPortNames.size == 1) { val inPortName = inPortNames.apply(0) - // Int32 is a placeholder message value val portCode: ST = st"""self.${portName}_publisher_ = self.create_publisher( | ${portType}, | "${inPortName}", | 1) - |""" + """ return portCode } @@ -588,7 +593,7 @@ object GeneratorPy { | ${portType}, | "${inPortName}", | 1) - |""" + """ counter = counter + 1 } @@ -605,24 +610,22 @@ object GeneratorPy { var publishers: ISZ[ST] = IS() if (inputPortCount == 1) { publishers = publishers :+ - st"self.${portName}_publisher_.publish(typedMsg)" + st"self.${portName}_publisher_.publish(msg)" } else { for (i <- 1 to inputPortCount) { publishers = publishers :+ - st"self.${portName}_publisher_${i}.publish(typedMsg)" + st"self.${portName}_publisher_${i}.publish(msg)" } } val publisherCode: ST = st"""def sendOut_${handlerName}(self, msg): | if type(msg) is ${portType}: - | typedMsg = ${portType}() - | typedMsg.data = msg | ${(publishers, "\n")} | else: | self.get_logger().error("Sending out wrong type of variable on port ${handlerName}.\nThis shouldn't be possible. If you are seeing this message, please notify this tool's current maintainer.") - |""" + """ return publisherCode } @@ -633,21 +636,28 @@ object GeneratorPy { if (isEventPort(portType)) { putMsgCode = - st"""def put_${handlerName}(self, msg): + st"""def put_${handlerName}(self): | self.enqueue(self.applicationOut_${handlerName}, ${portType}()) - |""" + """ } else { putMsgCode = st"""def put_${handlerName}(self, msg): - | typedMsg = ${portType}() - | typedMsg.data = msg - | self.enqueue(self.applicationOut_${handlerName}, typedMsg) - |""" + | self.enqueue(self.applicationOut_${handlerName}, msg) + """ } return putMsgCode } + def genPyDataPortInitializer(inDataPort: AadlPort): ST = { + val portName = inDataPort.identifier + + val initializer: ST = + st"""def init_${portName}(self, val): + | self.${portName}_msg_holder = val""" + return initializer + } + // Example: // self.subscription = self.create_subscription( // String, @@ -676,7 +686,7 @@ object GeneratorPy { | ${handler}, | 1, | callback_group=self.${callback_group_name}) - |""" + """ return portCode } @@ -692,7 +702,7 @@ object GeneratorPy { | ${handler}, | 1, | callback_group=self.${callback_group_name}) - |""" + """ counter = counter + 1 } @@ -707,33 +717,80 @@ object GeneratorPy { val handler: ST = st"""def event_handle_${handlerName}(self, msg): - | handle_${handlerName}() - |""" + | self.handle_${handlerName}() + """ return handler } + def genPyMessageToString(): ST = { + val message: ST = + st"""def message_to_string(self, msg): + | yaml_str = message_to_yaml(msg) + | return yaml_str + """ + + return message + } + + def genPySubscriptionHandlerSporadicWithExamples(inPort: AadlPort, portType: String, + inDataPorts: ISZ[AadlPort]): ST = { + val handlerName = inPort.identifier + + var exampleUsage: ST = st"" + if (inDataPorts.size > 0) { + exampleUsage = st"# Example receiving messages on data ports" + for (inDataPort <- inDataPorts) { + exampleUsage = + st"""${exampleUsage} + |${inDataPort.identifier} = get_${inDataPort.identifier}() + |self.get_logger().info(f"Received ${inDataPort.identifier}: {self.message_to_string(${inDataPort.identifier})}") + """ + } + } + + + var subscriptionHandlerHeader: ST = st"" + if (isEventPort(portType)) { + subscriptionHandlerHeader = st"""def handle_${handlerName}(self): + | # Handle ${handlerName} event + | self.get_logger().info("Received ${handlerName}") + """ + } + else { + subscriptionHandlerHeader = st"""def handle_${handlerName}(self, msg): + | # Handle ${handlerName} msg + | self.get_logger().info(f"Received ${handlerName}: {self.message_to_string(msg)}") + """ + } + + if (inDataPorts.size > 0) { + subscriptionHandlerHeader = + st"""${subscriptionHandlerHeader} + | + | ${exampleUsage}""" + } + + return subscriptionHandlerHeader + } + def genPySubscriptionHandlerPeriodic(inPort: AadlPort, portType: String): ST = { val handlerName = inPort.identifier - // Int32 is a placeholder message value val subscriptionHandlerHeader: ST = st"""def handle_${handlerName}(self, msg): - | typedMsg = ${portType}() - | typedMsg.data = msg - | self.${handlerName}_msg_holder = typedMsg - |""" + | self.${handlerName}_msg_holder = msg + """ return subscriptionHandlerHeader } def genPyGetSubscriptionMessage(inPort: AadlPort, nodeName: String): ST = { val portName = inPort.identifier - // Int32 is a placeholder message value val subscriptionMessage: ST = st"""def get_${portName}(self): | return self.${portName}_msg_holder - |""" + """ return subscriptionMessage } @@ -741,7 +798,7 @@ object GeneratorPy { val portName = inPort.identifier val subscriptionMessageVar: ST = - st"self.${portName}_msg_holder" + st"self.${portName}_msg_holder = None" return subscriptionMessageVar } @@ -753,12 +810,12 @@ object GeneratorPy { handlerCode = st"""def handle_${handlerName}(self): | raise NotImplementedError("Subclasses must implement this method") - |""" + """ } else { handlerCode = st"""def handle_${handlerName}(self, msg): | raise NotImplementedError("Subclasses must implement this method") - |""" + """ } return handlerCode @@ -768,7 +825,7 @@ object GeneratorPy { val portName = inPort.identifier val outMsgQueue: ST = - st"self.infrastructureOut_${portName} = Queue()" + st"self.infrastructureOut_${portName} = deque()" return outMsgQueue } @@ -776,7 +833,7 @@ object GeneratorPy { val portName = inPort.identifier val outMsgQueue: ST = - st"self.applicationOut_${portName} = Queue()" + st"self.applicationOut_${portName} = deque()" return outMsgQueue } @@ -797,12 +854,12 @@ object GeneratorPy { var publishers: ISZ[ST] = IS() if (inputPortCount == 1) { publishers = publishers :+ - st"self.${portName}_publisher_.publish(typedMsg)" + st"self.${portName}_publisher_.publish(msg)" } else { for (i <- 1 to inputPortCount) { publishers = publishers :+ - st"${portName}_publisher_${i}.publish(typedMsg)" + st"self.${portName}_publisher_${i}.publish(msg)" } } @@ -811,18 +868,15 @@ object GeneratorPy { if (isEventPort(portType)) { publisherCode = st"""def put_${handlerName}(self): - | typedMsg = ${portType}() - | + | msg = self.${portType}() | ${(publishers, "\n")} - |""" + """ } else { publisherCode = st"""def put_${handlerName}(self, msg): - | typedMsg = ${portType}() - | typedMsg.data = msg | ${(publishers, "\n")} - |""" + """ } return publisherCode } @@ -836,13 +890,13 @@ object GeneratorPy { def genPyTimeTriggeredBaseMethod(): ST = { val timeTriggered: ST = st"""def timeTriggered(self): - | pass + | raise NotImplementedError("Subclasses must implement this method") """ return timeTriggered } def genPyTimeTriggeredStrict(component: AadlThread): ST = { - val period = component.period.get + val period = component.period.get / 1000 val timer: ST = st"""self.periodTimer_ = self.create_timer(${period}, self.timeTriggeredCaller, callback_group=self.${callback_group_name})""" @@ -850,7 +904,7 @@ object GeneratorPy { } def genPyTimeTriggeredTimer(component: AadlThread): ST = { - val period = component.period.get + val period = component.period.get / 1000 val timer: ST = st"""self.periodTimer_ = self.create_timer(${period}, self.timeTriggered, callback_group=self.${callback_group_name})""" @@ -915,15 +969,15 @@ object GeneratorPy { def genPyReceiveInputsSporadic(): ST = { val method: ST = st"""def receiveInputs(self, infrastructureQueue, applicationQueue): - | if not(infrastructureQueue.empty()): - | eventMsg = infrastructureQueue.front() + | if not(len(infrastructureQueue) == 0): + | eventMsg = infrastructureQueue[0] | infrastructureQueue.pop() | self.enqueue(applicationQueue, eventMsg) | | for port in self.inDataPortTupleVector: | infrastructureQueue = port[0] - | if not(infrastructureQueue.empty()): - | msg = infrastructureQueue.front() + | if not(len(infrastructureQueue) == 0): + | msg = infrastructureQueue[0] | self.enqueue(port[1], msg) """ return method @@ -934,14 +988,14 @@ object GeneratorPy { st"""def receiveInputs(self): | for port in self.inDataPortTupleVector: | infrastructureQueue = port[0] - | if not(infrastructureQueue.empty()): - | msg = infrastructureQueue.front() - | self.enqueue(*port[1], msg) + | if not(len(infrastructureQueue) == 0): + | msg = infrastructureQueue[0] + | self.enqueue(port[1], msg) | | for port in self.inEventPortTupleVector: | infrastructureQueue = port[0] - | if not(infrastructureQueue.empty()): - | msg = infrastructureQueue.front() + | if not(len(infrastructureQueue) == 0): + | msg = infrastructureQueue[0] | infrastructureQueue.pop() | self.enqueue(port[1], msg) """ @@ -951,9 +1005,9 @@ object GeneratorPy { def genPyEnqueue(): ST = { val method: ST = st"""def enqueue(self, queue, val): - | if queue.size() >= 1: + | if len(queue) >= 1: | queue.pop() - | queue.push(val) + | queue.append(val) """ return method } @@ -963,15 +1017,15 @@ object GeneratorPy { st"""def sendOutputs(self): | for port in self.outPortTupleVector: | applicationQueue = port[0] - | if applicationQueue.size() != 0: - | msg = applicationQueue.front() + | if len(applicationQueue) != 0: + | msg = applicationQueue[0] | applicationQueue.pop() | self.enqueue(port[1], msg) | | for port in self.outPortTupleVector: | infrastructureQueue = port[1] - | if infrastructureQueue.size() != 0: - | msg = infrastructureQueue.front() + | if len(infrastructureQueue) != 0: + | msg = infrastructureQueue[0] | infrastructureQueue.pop() | (port[2])(msg) """ @@ -990,6 +1044,7 @@ object GeneratorPy { var publisherMethods: ISZ[ST] = IS() var subscriptionMessageGetters: ISZ[ST] = IS() var eventPortHandlers: ISZ[ST] = IS() + var dataPortInitializers: ISZ[ST] = IS() var outPortNames: ISZ[String] = IS() var inPortNames: ISZ[String] = IS() @@ -1009,6 +1064,10 @@ object GeneratorPy { } if (strictAADLMode) { if (p.direction == Direction.In) { + if (p.isInstanceOf[AadlDataPort]) { + dataPortInitializers = dataPortInitializers :+ genPyDataPortInitializerStrict(p, nodeName, portDatatype) + } + if (invertTopicBinding) { if (connectionMap.get(p.path).nonEmpty) { val outputPorts = connectionMap.get(p.path).get @@ -1073,6 +1132,10 @@ object GeneratorPy { } else { if (p.direction == Direction.In) { + if (p.isInstanceOf[AadlDataPort]) { + dataPortInitializers = dataPortInitializers :+ genPyDataPortInitializer(p) + } + if (invertTopicBinding) { if (connectionMap.get(p.path).nonEmpty) { val outputPorts = connectionMap.get(p.path).get @@ -1131,7 +1194,7 @@ object GeneratorPy { val typeIncludes: ISZ[ST] = genPyFileMsgTypeIncludes(packageName, msgTypes) var stdIncludes: ST = - st"""from queue import Queue""" + st"""from collections import deque""" if (strictAADLMode) { stdIncludes = @@ -1168,7 +1231,6 @@ object GeneratorPy { if (strictAADLMode) { fileBody = st"""${fileBody} - | MsgType = Union[${(msgTypes, ", ")}] | self.lock_ = threading.Lock() """ } @@ -1193,27 +1255,22 @@ object GeneratorPy { | ${genPyTimeTriggeredTimer(component)} """ } - - fileBody = - st"""${fileBody} - | ${genPyTimeTriggeredBaseMethod()}""" } - - if(strictAADLMode) { if (inMsgVars.size > 0) { fileBody = st"""${fileBody} | ${(inMsgVars, "\n")} - """ + """ } if (outMsgVars.size > 0) { fileBody = st"""${fileBody} | ${(outMsgVars, "\n")} - """ + """ } + if(strictAADLMode) { fileBody = st"""${fileBody} | # Used by receiveInputs @@ -1232,6 +1289,17 @@ object GeneratorPy { | ${genPyOutPortTupleVector(outPortNames)}""" } + if (dataPortInitializers.size > 0) { + fileBody = + st"""${fileBody} + | ${(dataPortInitializers, "\n\n")} + """ + } + + fileBody = + st"""${fileBody} + | ${genPyTimeTriggeredBaseMethod()}""" + if (subscriberMethods.size > 0 || publisherMethods.size > 0 || (strictAADLMode && subscribers.size > 0)) { fileBody = st"""${fileBody} @@ -1279,7 +1347,7 @@ object GeneratorPy { | # C o m p u t e E n t r y P o i n t | #================================================= | ${(subscriptionHandlerMethods, "\n")} - |""" + """ } if (strictAADLMode) { @@ -1304,27 +1372,23 @@ object GeneratorPy { return (filePath, fileBody, T, IS()) } - def genPySubscriptionHandlerAdder(inPort: AadlPort): ST = { - val handlerName = inPort.identifier - val subscriptionAdder: ST = st"node.handle_${handlerName} = handle_${handlerName}" - return subscriptionAdder - } - def genPySubscriptionHandlerSporadicStrict(inPort: AadlPort, portType: String): ST = { val handlerName = inPort.identifier var subscriptionHandlerHeader: ST = st"" if (isEventPort(portType)) { subscriptionHandlerHeader = - st"""def handle_${handlerName}(): - | pass # Handle ${handlerName} event - """ + st"""def handle_${handlerName}(self): + | # Handle ${handlerName} event + | self.get_logger().info("Received ${handlerName}") + """ } else { subscriptionHandlerHeader = - st"""def handle_${handlerName}(msg): - | pass # Handle ${handlerName} msg - """ + st"""def handle_${handlerName}(self, msg): + | # Handle ${handlerName} msg + | self.get_logger().info(f"Received ${handlerName}: {self.message_to_string(msg)}") + """ } return subscriptionHandlerHeader } @@ -1335,65 +1399,220 @@ object GeneratorPy { var subscriptionHandlerHeader: ST = st"" if (isEventPort(portType)) { subscriptionHandlerHeader = - st"""def handle_${handlerName}(): - | pass # Handle ${handlerName} event - """ + st"""def handle_${handlerName}(self): + | # Handle ${handlerName} event + | self.get_logger().info("Received ${handlerName}") + """ } else { subscriptionHandlerHeader = - st"""def handle_${handlerName}(msg): - | pass # Handle ${handlerName} msg - """ + st"""def handle_${handlerName}(self, msg): + | # Handle ${handlerName} msg + | self.get_logger().info(f"Received ${handlerName}: {self.message_to_string(msg)}") + """ } return subscriptionHandlerHeader } - def genPyTimeTriggeredMethod(): ST = { - val timeTriggered: ST = - st"""def timeTriggered(): - | pass # Handle communication + def genPySubscriptionHandlerSporadicStrictWithExamples(inPort: AadlPort, portType: String, + inDataPorts: ISZ[AadlPort]): ST = { + val handlerName = inPort.identifier + + var exampleUsage: ST = st"" + if (inDataPorts.size > 0) { + exampleUsage = st"# Example receiving messages on data ports" + for (inDataPort <- inDataPorts) { + exampleUsage = + st"""${exampleUsage} + |${inDataPort.identifier} = self.get_${inDataPort.identifier}() + |self.get_logger().info(f"Received ${inDataPort.identifier}: {self.message_to_string(${inDataPort.identifier})}") + """ + } + } + + var subscriptionHandlerHeader: ST = st"" + if (isEventPort(portType)) { + subscriptionHandlerHeader = st"""def handle_${handlerName}(self): + | # Handle ${handlerName} event + | self.get_logger().info("Received ${handlerName}") + """ + } + else { + subscriptionHandlerHeader = st"""def handle_${handlerName}(self, msg): + | # Handle ${handlerName} msg + | self.get_logger().info(f"Received ${handlerName}: {self.message_to_string(msg)}") + """ + } + + if (inDataPorts.size > 0) { + subscriptionHandlerHeader = + st"""${subscriptionHandlerHeader} + | + | ${exampleUsage}""" + } + + return subscriptionHandlerHeader + } + + def genPyExamplePublisher(outPort: AadlPort, packageName: String, + datatypeMap: Map[AadlType, (String, ISZ[String])], + reporter: Reporter): ST = { + val handlerName = outPort.identifier + val dataPortType: String = genPortDatatype(outPort, packageName, datatypeMap, reporter) + + var publisherCode: ST = st"" + + if (isEventPort(dataPortType)) { + publisherCode = + st"self.put_${handlerName}()" + } else { + publisherCode = + st"""${handlerName} = ${dataPortType}() + |self.put_${handlerName}(${handlerName}) + """ + } + + return publisherCode + } + + def genPyTimeTriggeredMethod(inDataPorts: ISZ[AadlPort], examplePublishers: ISZ[ST], + strictAADLMode: B): ST = { + var exampleUsage: ST = st"" + if (inDataPorts.size > 0) { + exampleUsage = st"# Example receiving messages on data ports" + for (inDataPort <- inDataPorts) { + + if (strictAADLMode) { + exampleUsage = + st"""${exampleUsage} + |${inDataPort.identifier} = self.get_${inDataPort.identifier}() + |self.get_logger().info(f"Received ${inDataPort.identifier}: {self.message_to_string(${inDataPort.identifier})}") + """ + } + else { + exampleUsage = + st"""${exampleUsage} + |${inDataPort.identifier} = self.get_${inDataPort.identifier}() + |self.get_logger().info(f"Received ${inDataPort.identifier}: {self.message_to_string(${inDataPort.identifier})}") + """ + } + } + } + + var timeTriggered: ST = + st"""def timeTriggered(self): + | # Handle communication """ + + if (inDataPorts.size > 0) { + timeTriggered = + st"""${timeTriggered} + | ${exampleUsage} + """ + } + + if (examplePublishers.nonEmpty) { + timeTriggered = + st"""${timeTriggered} + | # Example publishing messages + | ${(examplePublishers, "\n")}""" + } + return timeTriggered } + def genPyUserDataPortInitializers(inDataPorts: ISZ[AadlPort], packageName: String, + datatypeMap: Map[AadlType, (String, ISZ[String])], reporter: Reporter): ISZ[ST] = { + var initializers: ISZ[ST] = IS() + + for (p <- inDataPorts) { + val portDatatype = genPortDatatype(p, packageName, datatypeMap, reporter) + val portName = p.identifier + + initializers = initializers :+ + st"""${portName} = ${portDatatype}() + |self.init_${portName}(${portName}) + """ + } + + return initializers + } + def genPyUserNodePyFile(packageName: String, component: AadlThread, datatypeMap: Map[AadlType, (String, ISZ[String])], hasConverterFiles: B, strictAADLMode: B, reporter: Reporter): (ISZ[String], ST, B, ISZ[Marker]) = { val nodeName = genNodeName(component) val fileName = genPyNodeSourceName(nodeName) + var examplePublishers: ISZ[ST] = IS() + var inDataPorts: ISZ[AadlPort] = IS() + var msgTypes: ISZ[String] = IS() val startMarker: String = "# Additions within these tags will be preserved when re-running Codegen" val endMarker: String = "# Additions within these tags will be preserved when re-running Codegen" + for (p <- component.getPorts()) { + if (p.direction == Direction.Out) { + examplePublishers = examplePublishers :+ genPyExamplePublisher(p, packageName, datatypeMap, reporter) + } + else if (p.direction == Direction.In && p.isInstanceOf[AadlDataPort]) { + inDataPorts = inDataPorts :+ p + } + + val portDatatype: String = genPortDatatype(p, packageName, datatypeMap, reporter) + if (!ISZOps(msgTypes).contains(portDatatype)) { + msgTypes = msgTypes :+ portDatatype + } + } + var subscriptionHandlers: ISZ[ST] = IS() - var subscriptionAdders: ISZ[ST] = IS() if (isSporadic(component)) { + var firstSubscriptionHandler: B = true + for (p <- component.getPorts()) { val portDatatype: String = genPortDatatype(p, packageName, datatypeMap, reporter) if (p.direction == Direction.In && !p.isInstanceOf[AadlDataPort]) { if (strictAADLMode) { - subscriptionHandlers = subscriptionHandlers :+ - genPySubscriptionHandlerSporadicStrict(p, portDatatype) - subscriptionAdders = subscriptionAdders :+ - genPySubscriptionHandlerAdder(p) + if (firstSubscriptionHandler) { + subscriptionHandlers = subscriptionHandlers :+ + genPySubscriptionHandlerSporadicStrictWithExamples(p, portDatatype, inDataPorts) + firstSubscriptionHandler = false + } else { + subscriptionHandlers = subscriptionHandlers :+ + genPySubscriptionHandlerSporadicStrict(p, portDatatype) + } } else { - subscriptionHandlers = subscriptionHandlers :+ - genPySubscriptionHandlerSporadic(p, portDatatype) - subscriptionAdders = subscriptionAdders :+ - genPySubscriptionHandlerAdder(p) + if (firstSubscriptionHandler) { + subscriptionHandlers = subscriptionHandlers :+ + genPySubscriptionHandlerSporadicWithExamples(p, portDatatype, inDataPorts) + firstSubscriptionHandler = false + } else { + subscriptionHandlers = subscriptionHandlers :+ + genPySubscriptionHandlerSporadic(p, portDatatype) + } } } } } else { - subscriptionHandlers = subscriptionHandlers :+ genPyTimeTriggeredMethod() - subscriptionAdders = subscriptionAdders :+ st"node.timeTriggered = timeTriggered" + subscriptionHandlers = subscriptionHandlers :+ genPyTimeTriggeredMethod(inDataPorts, examplePublishers, strictAADLMode) } + val inDataPortInitializers: ISZ[ST] = genPyUserDataPortInitializers(inDataPorts, packageName, datatypeMap, reporter) + + val typeIncludes: ISZ[ST] = genPyFileMsgTypeIncludes(packageName, msgTypes) + var includeFiles: ST = st"""#!/usr/bin/env python3 |import rclpy - |from rclpy.node import Node""" + |from rclpy.node import Node + |from rosidl_runtime_py.convert import message_to_yaml + |from ${packageName}.base_code.${nodeName}_base_src import ${nodeName}_base""" + + if (typeIncludes.size != 0) { + includeFiles = + st"""${includeFiles} + |${(typeIncludes, "\n")}""" + } if (hasConverterFiles) { includeFiles = @@ -1401,27 +1620,50 @@ object GeneratorPy { |from ${packageName}.base_code.enum_converter import *""" } - val fileBody = + var fileBody = st"""${includeFiles} | |#=========================================================== |# This file will not be overwritten when re-running Codegen |#=========================================================== + |class ${nodeName}(${nodeName}_base): + | def __init__(self): + | super().__init__() + | # invoke initialize entry point + | self.initialize() + | + | self.get_logger().info("${nodeName} infrastructure set up") | |#================================================= |# I n i t i a l i z e E n t r y P o i n t |#================================================= - |def initialize(node): - | node.get_logger().info("Initialize Entry Point invoked") + | def initialize(self): + | self.get_logger().info("Initialize Entry Point invoked") | - | # Initialize the node - | ${(subscriptionAdders, "\n")} - | - |#================================================= - |# C o m p u t e E n t r y P o i n t - |#================================================= - |${(subscriptionHandlers, "\n")} + | # Initialize the node | + | # Initialize the node's incoming data port values here + """ + + if (inDataPortInitializers.size != 0) { + fileBody = + st"""${fileBody} + | # Initialize the node's incoming data port values here + | ${(inDataPortInitializers, "\n")}""" + } + + fileBody = + st"""${fileBody} + | + |#================================================= + |# C o m p u t e E n t r y P o i n t + |#================================================= + | ${genPyMessageToString()} + | ${(subscriptionHandlers, "\n")} + """ + + fileBody = + st"""${fileBody} |#================================================= |# Include any additional declarations here |#================================================= @@ -1432,7 +1674,7 @@ object GeneratorPy { val filePath: ISZ[String] = IS("src", packageName, packageName, "user_code", fileName) - return (filePath, fileBody, F, IS(Marker(startMarker, endMarker))) + return (filePath, fileBody, F, IS()) } def genPyNodeRunnerName(compNameS: String): String = { @@ -1450,20 +1692,10 @@ object GeneratorPy { |import rclpy |from rclpy.node import Node |from rclpy.executors import MultiThreadedExecutor - |from ${packageName}.user_code.${nodeName}_src import initialize - |from ${packageName}.base_code.${nodeName}_base_src import ${nodeName}_base + |from ${packageName}.user_code.${nodeName}_src import ${nodeName} |#======================================================== |# Re-running Codegen will overwrite changes to this file |#======================================================== - | - |class ${nodeName}(${nodeName}_base): - | def __init__(self): - | super().__init__() - | # invoke initialize entry point - | initialize(self) - | - | self.get_logger().info("${nodeName} infrastructure set up") - | |def main(args=None): | rclpy.init(args=args) | node = ${nodeName}() @@ -1522,7 +1754,7 @@ object GeneratorPy { | ${(cases, "\n")} | case default: | return "Unknown value for ${enumName}" - """ + """ } else { converters = converters :+ @@ -1533,7 +1765,7 @@ object GeneratorPy { | ${(cases, "\n")} | case default: | return "Unknown value for ${enumName}" - """ + """ } } From 2ba50591bbda8cf6022f894805920a60836121ef Mon Sep 17 00:00:00 2001 From: CatLiSantos Date: Fri, 25 Jul 2025 17:31:02 -0500 Subject: [PATCH 17/18] Fixing minor bug --- .DS_Store | Bin 6148 -> 6148 bytes jvm/.DS_Store | Bin 6148 -> 6148 bytes jvm/src/.DS_Store | Bin 6148 -> 6148 bytes jvm/src/test | 2 +- .../hamr/codegen/ros2/GeneratorPy.scala | 2 +- 5 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.DS_Store b/.DS_Store index edc6312b638bf5c6e7057b82544d4b19cad78450..9f56e1e43da26050b6d0ccf7dd5b43c2319d15db 100644 GIT binary patch delta 21 ccmZoMXffE3!Ny@?WT2y9Y+|{&fNhE(07I7r-v9sr delta 21 ccmZoMXffE3!Ny^1XrZHEY;3f-fNhE(07In(-2eap diff --git a/jvm/.DS_Store b/jvm/.DS_Store index 4d27c2c3c061307320ed6f05df791ea5bad9d8e5..41c5d9c094e6494d6622cfac2a889242b62eb8da 100644 GIT binary patch delta 21 ccmZoMXffEZhl#_)$UsNI*u--45he#w07*LrF8}}l delta 21 ccmZoMXffEZhl#`3&_YMS*w|?E5he#w07*#(EdT%j diff --git a/jvm/src/.DS_Store b/jvm/src/.DS_Store index 354c21c28457880336f72977e123aeff74ad9898..cd38025ca08535c484fd764ea4d251152cd3336f 100644 GIT binary patch delta 21 ccmZoMXffDuftAC=$UsNI*u--44OSf?07}*dMgRZ+ delta 21 ccmZoMXffDuftAD9&_YMS*w|?E4OSf?07~QrL;wH) diff --git a/jvm/src/test b/jvm/src/test index 6d94f05e..c9ef61df 160000 --- a/jvm/src/test +++ b/jvm/src/test @@ -1 +1 @@ -Subproject commit 6d94f05e00304ad467b84f81c283f70854fb3bc4 +Subproject commit c9ef61df144a2e68824d85eb7924bcbb6ae217f0 diff --git a/shared/src/main/scala/org/sireum/hamr/codegen/ros2/GeneratorPy.scala b/shared/src/main/scala/org/sireum/hamr/codegen/ros2/GeneratorPy.scala index 64bfd58f..473711df 100644 --- a/shared/src/main/scala/org/sireum/hamr/codegen/ros2/GeneratorPy.scala +++ b/shared/src/main/scala/org/sireum/hamr/codegen/ros2/GeneratorPy.scala @@ -868,7 +868,7 @@ object GeneratorPy { if (isEventPort(portType)) { publisherCode = st"""def put_${handlerName}(self): - | msg = self.${portType}() + | msg = ${portType}() | ${(publishers, "\n")} """ } From 3bdcd43d7237dde27167e249733adea545dd8663 Mon Sep 17 00:00:00 2001 From: CatLiSantos Date: Fri, 25 Jul 2025 17:33:09 -0500 Subject: [PATCH 18/18] Removing DS_Store --- .DS_Store | Bin 6148 -> 0 bytes jvm/.DS_Store | Bin 6148 -> 0 bytes 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 .DS_Store delete mode 100644 jvm/.DS_Store diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 9f56e1e43da26050b6d0ccf7dd5b43c2319d15db..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHKO>fgc5S>jz<0PdD5=gxuS>jrSK%+_(mk`ne2QCeQ1E3JsR^#G$qu6PXqDVQz zf8Z~0<(KeZIKi9sCh|u(RYIuRiFV(5J#T0CdF;&+k?76cCqzvmvQQW+byPcy?{g_w zN%!0X3LfKSATOLiI=JQmjTr@u0{^!H{O(q$MVI7LoPB@AL)>fqR;Ctlu<@f2#rVy@ zacGW~M^TRZ3yg0mZgtBYgW+VoOP4wC`qtaW%tDfgKOA-V*%XhLq5SI~72JPALZcP@tb znu}4UE4rphZbVi){iL*)vxuhpd;G(h27VN`+doCAT)DGXwW?Ob`Y;&F#ULKV^QbqP zz2mFbQU=bZJ#yao-eTCi|3ZfG$Pc}lBKWQcm-lb|(3OjxoQH0txP#<|RkLct=HYVL zdD=d*k2|NUBYSzu?auMZYE`oyJbL`>eE2Dtgz^h!4infvl-M4#PoUR(@*E_K24D$63Tms7bDUYA{L$%=T;`?YjjR$ z$fZ8L0`8cf%7k>JJ&em4glsQl4J(oM#pMl6Yi>1eIF6wk1&ji>PXYcu*eHx$jq3!} zrvsHb0st%MR))6v>kqWK1K8ELP7pmXkx+pNRp=vz&?g6x&~d-3{nrU5bP_5u#?g_5 zKA{MG@DR3gC(%vN)J6fLKvscra@pkl|LpJQ|18N|83l|2w@Lv~=@0rnEJ^RJg~jn+ vYomNbVPn5_f{KDlZ^x>^Tk#=^GTgJd0qkmACx{-H`6HlYFqKi@pDOSNIN1*1 diff --git a/jvm/.DS_Store b/jvm/.DS_Store deleted file mode 100644 index 41c5d9c094e6494d6622cfac2a889242b62eb8da..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHK!AiqG5Z!I7O(;SR3OxqA7Ol1j#Y>F!;MIs8RBF-`4aTe_sX3HF&iX@siNB*W zyBncYFCGNT49vdC?97IF8+J2{G44*oJ;p4?m;s7dGhz5fa2|C@YT8o=F-^iu=6Y(v zFb#8LwP&;bez#-y`h$7Lo(<6M_YUTB)7aYHJvtl$+4UCpzX zCb3BFz*9w6QG~<*F+dEgE(7|+Gg_-#B2`8V5Cgwt0M7>-6w%RGDwIbD46+3PY=Bz| z*z)He&_)NKqp?&79uTfl0aYs3Ee6-U16S!V@96kap-N|5z8TihZ|1r~;kxzUwhU+7 zQAi^(KnyH0fW04xh4=r-&;7p)qJbD723C^+-WYmA7i!aY>r$0?*V>@BpePtuD*Q?T jLte!Yi&t?QR0{ZQGyolqr9$w4(2szmfd*pWPZ{_G&>T|d