F´(F Prime)Subtopology 开发指南:统一 FPP 拓扑、Phases 配置与部署集成

F´(F Prime)Subtopology 开发指南:统一 FPP 拓扑、Phases 配置与部署集成 F´F PrimeSubtopology 开发指南统一 FPP 拓扑、Phases 配置与部署集成【免费下载链接】fprimeF´ - A flight software and embedded systems framework项目地址: https://gitcode.com/GitHub_Trending/fpr/fprimeSubtopology子拓扑是 F´ 框架中用于将可复用的拓扑片段打包并在基础部署中导入的一种架构手段它把组件实例声明、实例定义、init 规格与连线统一收敛到单个.fpp文件中并通过 Phases阶段机制替代传统的topology.cpp/hpp配置模式。本文以 RNG 遥测组件为例完整讲解子拓扑的目录结构、统一 FPP 写法、TopologyState定义、CMake 注册、配置模块拆分以及在主部署中的集成全流程并对照仓库中 Svc/Subtopologies 下的官方实现给出源码级佐证读者学完后可以直接动手编写并集成自己的可复用子拓扑。为什么需要 SubtopologySubtopology 服务于 F´ 中更小行为块的拓扑组织把彼此契合的一小段拓扑架构组件实例 连线 初始化规格打包成一个独立单元再被导入到基础部署deployment的顶层拓扑中。它的典型使用场景是与可共享组件结合即把组件 围绕该组件的最小拓扑作为整体发布相关背景可参考 开发 F´ 库develop-fprime-libraries。从仓库中可以看到F´ 官方已经在 Svc/Subtopologies 下提供了CdhCore、ComCcsds、ComCcsdsSdls、ComFprime、FileHandling、FileHandlingCfdp、DataProducts、DpCompression、ComLoggerTee等多个现成的子拓扑其中 Svc/Subtopologies/CMakeLists.txt 通过add_fprime_subdirectory逐个引入并汇总出一个Svc_Subtopologies自定义目标custom target来统一承载依赖——这即是子拓扑打包复用理念在官方代码库中的落地形态。Subtopology 结构一个子拓扑是一个文件夹其中包含topology.cpp构建生成包含topology.fpp的 C 实现实际由 FPP 自动代码生成器产出topology.fpp统一 FPP 文件unified拓扑文件把连线wiring、组件实例component instances与 init 规格init specifications都封装在一个文件里取代实例文件与拓扑文件分离的传统写法topologydefs.hpp拓扑定义头定义一个struct即TopologyState用于实例化子拓扑时向其中注入状态CMakeLists.txt把topology.cpp链接到项目构建中。因此子拓扑的必需结构为MySubtopology/ ├─ CMakeLists.txt ├─ MySubtopology.fpp └─ MySubtopologyTopologyDefs.hpp子拓扑通常还会随附配置模块格式扩展为MySubtopology/ ├─ MySubtopologyConfig/ │ ├─ CMakeLists.txt │ └─ MySubtopologyConfig.fpp ├─ CMakeLists.txt ├─ MySubtopology.fpp └─ MySubtopologyTopologyDefs.hpp此外还有可选的扩展文件强烈建议包含docs文件夹用简单的 Markdown 文件即子拓扑设计文档sdd记录设计仓库中各官方子拓扑均带有docs/sdd.md其他.fpp文件也可包含在子拓扑中单元测试可参照组件单元测试的结构加入子拓扑。注意在最新版的fprime-tools中可以直接运行fprime-util new --subtopology生成子拓扑结构。Individual File Contents本节逐一介绍组成子拓扑的各个文件的内容。示例文件名沿用上文必需结构中的命名但你自己的子拓扑不必拘泥于这些文件名。示例场景假设我们要创建一个名为RNG的组件当它被挂到 1Hz 时钟rate group上时向某个遥测通道写入一个随机整数。RNG组件有一个名为run的输入端口作为来自时钟rate group的入口RNG是 active 组件位于MyLibrary命名空间下。MySubtopology.fpp如前所述这是统一.fpp文件既包含子拓扑所用组件的实例声明instance declarations也包含实例定义instance definitions。基于示例场景实例声明如下instance rng // RNG component instance instance rateGroup // rate group instance对应的实例定义如下instance rng: MyLibrary.RNG base id 0xFF2FF \ queue size Defaults.QUEUE_SIZE \ stack size Defaults.STACK_SIZE \ priority 100 instance rateGroup: Svc.ActiveRateGroup base id 0xFF4FF \ queue size Defaults.QUEUE_SIZE \ stack size Defaults.STACK_SIZE \ priority 150以及如下连线connections MyWiring { // we will hook up the cycle for our rate group later on rateGroup.RateGroupMemberOut[0] - rng.run }于是统一 FPP 文件MySubtopology.fpp可以写成module MySubtopology { instance rng: MyLibrary.RNG base id 0xFF2FF \ queue size Defaults.QUEUE_SIZE \ stack size Defaults.STACK_SIZE \ priority 100 instance rateGroup: Svc.ActiveRateGroup base id 0xFF4FF \ queue size Defaults.QUEUE_SIZE \ stack size DEFAULTS.STACK_SIZE \ priority 150 topology MySubtopology { instance rng // RNG component instance instance rateGroup // rate group instance connections MyWiring { rateGroup.RateGroupMemberOut[0] - rng.run } } // end topology } // end MySubtopology仓库中的官方示例完全验证了这一写法。例如 Svc/Subtopologies/CdhCore/CdhCore.fpp 在module CdhCore内声明了cmdDisp、events、$health、version等实例每个实例都使用CdhCoreConfig.BASE_ID 0xNNN000的形式分配 base id并显式给出queue size、stack size、priority、cpu随后定义topology Subtopology在拓扑体内再次列出使用的实例instance cmdDisp等并通过connections FaultProtection { events.FatalAnnounce - fatalHandler.FatalReceive }建立连线。值得注意的还有子拓扑中的拓扑端口topology ports官方CdhCore.fpp在topology Subtopology内部用port关键字把子拓扑内部端口暴露为外部接口例如 Input port for receiving command buffers from sequencers or other command buffer sources port seqCmdBuff cmdDisp.seqCmdBuff Output port for sending event packets from the EventManager port eventsPktSend events.PktSend这与文档中的连线示例相互补充——连线解决实例之间的内部连接port声明则解决子拓扑与外部世界主部署的接口契约。统一 FPP 与 Phases阶段Phases 是一种基于组件实例编写 C 配置函数的方式目标是让单个统一 FPP 文件取代topology.cpp/hpp文件对。对于子拓扑F´ 强制使用 Phases以保持子拓扑的一致性与简洁性。在传统的 cpp/hpp 配置模型中拓扑通过以下三个函数完成初始化namespace MySubtopology{ void configureTopology(const TopologyState state) { // ... your code here ... } void startTopology(const TopologyState state) { // ... your code here ... } void teardownTopology(const TopologyState state) { // ... your code here ... } }在 Phases 模式中这三个函数以及其他阶段详见 FPP 用户指南中关于实例 init 规格的定义被绑定为 FPP 组件实例的 init specifiers。于是不再在一个地方集中配置所有组件而是每个实例定义各自的 init 规格。写法如下可直接内联在统一 FPP 文件中passive component C1 instance c1: C1 base id 0x4444 \ { phase Fpp.ToCpp.Phases.configComponents // your cpp code here for configuring }子拓扑强制使用 Phases。需要特别指出的是Phases 依然可以接收TopologyState state变量。TopologyState正是那个结构体可以包含任何你希望开发者能够修改的变量从而为子拓扑带来动态性dynamic-ness。官方仓库对 Phases 的使用十分充分。以 Svc/Subtopologies/CdhCore/CdhCore.fpp 中的$health实例为例instance $health: Svc.Health base id CdhCoreConfig.BASE_ID 0x002000 \ queue size CdhCoreConfig.QueueSizes.$health \ { phase Fpp.ToCpp.Phases.configConstants enum { HEALTH_WATCHDOG_CODE 0x123 }; phase Fpp.ToCpp.Phases.configComponents // Health is supplied a set of ping entires. CdhCore::health.setPingEntries( ConfigObjects::CdhCore_health::pingEntries, FW_NUM_ARRAY_ELEMENTS(ConfigObjects::CdhCore_health::pingEntries), ConfigConstants::CdhCore_health::HEALTH_WATCHDOG_CODE ); }这里用configConstants阶段定义看门狗代码常量用configComponents阶段调用setPingEntries注入 ping 表项version实例则分别用configComponents阶段调用config(true)、用startTasks阶段调用start()。可以看到实例名在生成的 C 代码中通过命名空间前缀限定为CdhCore::health、CdhCore::version这与下文命名映射一节完全对应。MySubtopologyTopologyDefs.hpp在示例中我们希望允许使用者自定义随机数生成器的初始种子struct TopologyState { // ... U32 initialSeed; // ... }该结构体定义放在MySubtopologyDefs.hpp即MySubtopologyTopologyDefs.hpp中。此外有时还需要在Defs.hpp中引入全局定义视你的子拓扑而定这部分可能是可选的。示例如下// Example; may not be required namespace GlobalDefs { namespace PingEntries { MySubtopology_rateGroup { enum { WARN 3, ERROR 5 }; } } // end PingEntries } // GlobalDefs注意PingEntries被包裹在名为GlobalDefs的命名空间中。这很重要它允许我们在多个拓扑中共用GlobalDefs命名空间然后在主部署中把全部定义链接到一起。这一点在下一节集成到主部署中会更加清晰。仓库中的真实定义可参见 Svc/Subtopologies/CdhCore/SubtopologyTopologyDefs.hpp其中定义了namespace CdhCore下的SubtopologyState空结构体与TopologyState包含SubtopologyState cdhCore并 include 了自动生成的FppConstantsAc.hpp以引用配置常量。而 Svc/Subtopologies/CdhCore/PingEntries.hpp 则把 ping 表项按实例命名为CdhCore_cmdDisp、CdhCore_events、CdhCore_tlmSend每个都带WARN/FATAL枚举值——这正是文档中GlobalDefs思路的实际实现。这引出了子拓扑变量命名的一套独特约定。在子拓扑开发端我们看到的是configureTopology之类的名字但当这些子拓扑被折叠进更大的项目时构建端这些函数会被按其名字加入命名空间以免函数尤其是组件实例之间相互混淆。映射关系如下fppcpp syntaxMySubtopology.rateGroupMySubtopology_rateGroupCMake 与构建文件最后一步是添加 CMake 相关文件即CMakeLists.txt。其结构高度可复用本质上只是告知编译器构建某个部署时存在该拓扑。CMakeLists.txt内容register_fprime_module( EXCLUDE_FROM_ALL AUTOCODER_INPUTS ${CMAKE_CURRENT_LIST_DIR}/RNGTopology.fpp HEADERS ${CMAKE_CURRENT_LIST_DIR}/MySubtopologyTopologyDefs.hpp INTERFACE )EXCLUDE_FROM_ALL表示该模块不进入默认全量构建而是由依赖它的部署显式拉起。官方实现完全一致见 Svc/Subtopologies/CdhCore/CMakeLists.txt其中把CdhCore.fpp作为AUTOCODER_INPUTS、把SubtopologyTopologyDefs.hpp与PingEntries.hpp作为HEADERS注册。集成到主部署至此可以把子拓扑集成到主部署的拓扑中了可以把主部署视为最终交付物MySubtopology只是其中的一部分。假设你已经随拓扑一起开发了 RNG 组件并已创建好一个 F´ 项目及其部署部署名为MainDeployment项目名为MainProject。第一步把子拓扑链接进项目。在project.cmake中、添加主拓扑那行之前加入# before the line adding your main topology add_fprime_subdirectory(${CMAKE_CURRENT_LIST_DIR}/MySubtopology/)第二步处理MainDeploymentTopologyDefs.hpp。既要 include 子拓扑的定义头也要把PingEntires改为使用GlobalDefs::PingEntries。在namespace MainDeployment末尾加入namespace PingEntries GlobalDefs::PingEntries;然后把当前的PingEntries命名空间调用改为被 GlobalDefs 包裹namespace GlobalDefs { namespace PingEntries { namespace blockDrv { enum { WARN 3, FATAL 5 }; } ...第三步让主部署拓扑导入子拓扑。同时把主部署 rate group driver 的CycleOut端口接到子拓扑 rate group 的CycleIn端口注意 rate group driver 的输出端口数组要有足够大小。在topology.fpp中加入... topology MainDeployment { import MySubtopology.MySubtopology ... connections RateGroups{ ... rateGroupDriver.CycleOut[3] - MySubtopology.rateGroup.CycleIn # youll notice that the syntax here is Namespace.instance ... } } ...这里import MySubtopology.MySubtopology的语法是Namespace.TopologyName而连接目标端口时用的是Namespace.instance。第四步在MainDeploymentTopology.cpp中调用子拓扑的 configure/start/teardown 函数。示例// at the top, include our topology.hpp #include MySubtopology/MySubtopologyTopology.hpp ... // MODIFY this line to include the 4th divider Svc::RateGroupDriver::DividerSet rateGroupDivisorsSet{{{1, 0}, {2, 0}, {4, 0}, {1, 0}}}; void configureTopology() { ... MySubtopology::TopologyState myState; myState.clockRate 1; // we set our clock rate to whatever standard we want MySubtopology::configureTopology(myState); } ... void setupTopology(const TopologyState state){ ... MySubtopology::startTopology({}); } ... void teardownTopology(const TopologyState state){ ... MySubtopology::teardownTopology({}) }第五步处理遥测打包。由于 RNG 组件带遥测通道需要在部署文件夹内的Packets.fppi中 include或忽略这些通道。与其他加入部署的组件一样使用同样的语法实例名 遥测通道名。之后即可运行并构建你的部署得到的是一个使用了子拓扑的构建产物。为子拓扑添加配置Config 模块为子拓扑添加配置的方法是在子拓扑目录下新增一个模块通常命名为追加Config的形式如MySubtopologyConfig。该目录至少包含一个CMakeLists.txt与可配置文件。在本例中有两处需要配置子拓扑的 base ID组件属性队列深度queue depth、栈大小stack size、优先级priority与 CPU 亲和性CPU affinity可在MySubtopologyConfig.fpp中完成如下所示module MySubtopologyConfig { #Base ID for the CdhCore Subtopology, all components are offsets from this base ID constant BASE_ID 0xA0000000 module QueueSizes { constant rng 10 constant rateGroup 10 } module StackSizes { constant rng 64 * 1024 constant rateGroup 64 * 1024 } module Priorities { constant rng 89 constant rateGroup 90 } module CpuAffinities { constant rng Os.TASK_DEFAULT constant rateGroup Os.TASK_DEFAULT } }MySubtopology.fpp必须更新为使用该配置instance rng: MyLibrary.RNG base id MySubtopologyConfig.BASE_ID 0x1000 \ queue size MySubtopologyConfig.QueueSizes.rng \ stack size MySubtopologyConfig.StackSizes.rng \ priority MySubtopologyConfig.Priorities.rng \ cpu MySubtopologyConfig.CpuAffinities.rng instance rateGroup: Svc.ActiveRateGroup base id MySubtopologyConfig.BASE_ID 0x2000 \ queue size MySubtopologyConfig.QueueSizes.rateGroup \ stack size MySubtopologyConfig.StackSizes.rateGroup \ priority MySubtopologyConfig.Priorities.rateGroup \ cpu MySubtopologyConfig.CpuAffinities.rateGroup[!IMPORTANT] 配置值应按组件的每个实例per-instance分别设置。接下来为子拓扑配置模块添加CMakeLists.txtregister_fprime_config( EXCLUDE_FROM_ALL AUTOCODER_INPUTS ${CMAKE_CURRENT_LIST_DIR}/MySubtopologyConfig.fpp INTERFACE )然后更新子拓扑使其依赖配置模块register_fprime_module( EXCLUDE_FROM_ALL AUTOCODER_INPUTS ${CMAKE_CURRENT_LIST_DIR}/RNGTopology.fpp HEADERS ${CMAKE_CURRENT_LIST_DIR}/MySubtopologyTopologyDefs.hpp DEPENDS MySubtopology_MySubtopologyConfig INTERFACE )完成以上步骤后使用者就可以配置你的子拓扑了。官方代码库完整演示了这套配置拆分模式。看 Svc/Subtopologies/CdhCore/CdhCoreConfig/CdhCoreConfig.fppBASE_ID 0x01000000QueueSizes、StackSizes、Priorities、CpuAffinities四个子模块分别给出各实例的取值如$health的队列 25、优先级 24cmdDisp栈 64 * 1024CPU 亲和性用Os.TASK_DEFAULTSvc/Subtopologies/CdhCore/CdhCoreConfig/CMakeLists.txt 使用register_fprime_config注册该配置模块Svc/Subtopologies/CdhCore/CMakeLists.txt 在注册子拓扑模块时通过DEPENDS Svc_Subtopologies_CdhCore_CdhCoreConfig声明依赖并先行add_fprime_subdirectory(${CMAKE_CURRENT_LIST_DIR}/CdhCoreConfig/)。ComCcsds、DataProducts、FileHandlingCfdp等子拓扑均遵循同一模式。Conclusion本指南完整走通了子拓扑的开发流程从目录结构与统一 FPP 文件、Phases 配置机制、TopologyState定义与命名映射到 CMake 注册、配置模块拆分以及主部署中的 import、端口连接与生命周期函数调用。一个部署可以包含多个不同的子拓扑因此该特性真正让 F´ 在快速原型quick prototyping场景下变得更加易用。想进一步动手实践可以研究仓库中 Svc/Subtopologies 下现成的官方子拓扑每个都附带docs/sdd.md设计文档与 Config 模块并参考 F´ 部署项目中project.cmake与topology.fpp的实际写法进行集成。【免费下载链接】fprimeF´ - A flight software and embedded systems framework项目地址: https://gitcode.com/GitHub_Trending/fpr/fprime创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考