diff --git a/Svc/Ports/TlmPacketizerPorts/TlmPacketizerPorts.fpp b/Svc/Ports/TlmPacketizerPorts/TlmPacketizerPorts.fpp index da0afc71b52..f57c428651b 100644 --- a/Svc/Ports/TlmPacketizerPorts/TlmPacketizerPorts.fpp +++ b/Svc/Ports/TlmPacketizerPorts/TlmPacketizerPorts.fpp @@ -19,4 +19,27 @@ module Svc { minDelta: U32 @< Minimum Sched Ticks to send packets on updates when using ON_CHANGE logic maxDelta: U32 @< Maximum Sched Ticks between packets to send when using EVERY_MAX logic ) + + @ Maximum number of per-packet config entries carried in a single push batch. + @ Sized so one fully-serialized batch stays well within FW_COM_BUFFER_MAX_SIZE. + constant TLM_PACKET_CONFIG_BATCH_MAX = 32 + + @ A single per-packet configuration record: which packet, which section, and its policy. + @ packetId matches the packet id used by the TlmPacketizer SEND_PKT command. + struct PacketConfigEntry { + packetId: U32 @< Packet identifier + section: TelemetrySection @< Section the policy applies to + config: PacketConfig @< Enable + rate policy for this packet/section + } + + @ A fixed-capacity batch of per-packet config entries. Only the first `count` are valid. + array PacketConfigBatch = [TLM_PACKET_CONFIG_BATCH_MAX] PacketConfigEntry + + @ Port pushing a batch of per-packet configuration from the config persistent-memory owner + @ to the packetizer (TlmPacketizer). Batching bounds the number of async messages required + @ to synchronize many packets (e.g. the full push at boot). + port TlmPacketConfigUpdate( + count: FwSizeType @< Number of valid entries in `batch`, in [0, TLM_PACKET_CONFIG_BATCH_MAX] + batch: PacketConfigBatch @< Batch of config entries; entries [0, count) are valid + ) } diff --git a/Svc/TlmPacketizer/CMakeLists.txt b/Svc/TlmPacketizer/CMakeLists.txt index c999bd92160..f9aadde6604 100644 --- a/Svc/TlmPacketizer/CMakeLists.txt +++ b/Svc/TlmPacketizer/CMakeLists.txt @@ -15,6 +15,8 @@ register_fprime_module( "${CMAKE_CURRENT_LIST_DIR}/TlmPacketizer.cpp" DEPENDS Svc_TlmPacketizer_config_TlmPacketizerConfig + Svc_Ports_TlmPacketizerPorts + Svc_Types_TlmPacketizerTypes ) diff --git a/Svc/TlmPacketizer/TlmPacketizer.cpp b/Svc/TlmPacketizer/TlmPacketizer.cpp index dd032b6ea37..e24ad30183d 100644 --- a/Svc/TlmPacketizer/TlmPacketizer.cpp +++ b/Svc/TlmPacketizer/TlmPacketizer.cpp @@ -305,8 +305,8 @@ void TlmPacketizer ::Run_handler(const FwIndexType portNum, U32 context) { for (FwIndexType section = 0; section < TelemetrySection::NUM_SECTIONS; section++) { PktSendCounters& pktEntryFlags = this->m_packetFlags[static_cast(section)][pkt]; - TlmPacketizer_GroupConfig& entryGroupConfig = - this->m_groupConfigs[static_cast(section)][entryGroup]; + // Per-packet override if one is set (via ENABLE/FORCE/CONFIGURE_PACKET*), else the group-derived policy. + const Svc::PacketConfig entryGroupConfig = this->effectiveConfig(section, pkt, entryGroup); // Packet is updated and not REQUESTED (Keep REQUESTED marking to bypass disable checks) if (isNewData && pktEntryFlags.updateFlag != UpdateFlag::REQUESTED) { @@ -535,6 +535,85 @@ void TlmPacketizer ::CONFIGURE_GROUP_RATES_cmdHandler(FwOpcodeType opCode, this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK); } +void TlmPacketizer ::GET_PACKET_CONFIG_cmdHandler(FwOpcodeType opCode, + U32 cmdSeq, + U32 packetId, + const Svc::TelemetrySection& section) { + FW_ASSERT(section.isValid()); + if (section < 0 or section >= TelemetrySection::NUM_SECTIONS) { + this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::VALIDATION_ERROR); + return; + } + FwChanIdType pkt = 0; + if (not this->findPacketIndexById(packetId, pkt)) { + this->log_WARNING_LO_UnknownPacketId(packetId); + this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::VALIDATION_ERROR); + return; + } + const Svc::PacketConfig eff = + this->effectiveConfig(static_cast(section.e), pkt, this->m_fillBuffers[pkt].level); + Svc::PacketConfigEntry entry; + entry.set_packetId(packetId); + entry.set_section(section.e); + entry.set_config(eff); + this->tlmWrite_QueriedPacketConfig(entry); + this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK); +} + +void TlmPacketizer ::ENABLE_PACKET_cmdHandler(FwOpcodeType opCode, + U32 cmdSeq, + U32 packetId, + const Svc::TelemetrySection& section, + const Fw::Enabled& enable) { + FwSizeType s = 0; + FwChanIdType pkt = 0; + // Validate on ground data (no assert): bad enable / section / unknown id -> VALIDATION_ERROR. + if (not enable.isValid() or not this->resolveAndSeedOverride(section, packetId, s, pkt)) { + this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::VALIDATION_ERROR); + return; + } + this->m_packetOverride[s][pkt].set_enabled(enable); + this->mirrorOverride(section, pkt, packetId); + this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK); +} + +void TlmPacketizer ::FORCE_PACKET_cmdHandler(FwOpcodeType opCode, + U32 cmdSeq, + U32 packetId, + const Svc::TelemetrySection& section, + const Fw::Enabled& enable) { + FwSizeType s = 0; + FwChanIdType pkt = 0; + if (not enable.isValid() or not this->resolveAndSeedOverride(section, packetId, s, pkt)) { + this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::VALIDATION_ERROR); + return; + } + this->m_packetOverride[s][pkt].set_forceEnabled(enable); + this->mirrorOverride(section, pkt, packetId); + this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK); +} + +void TlmPacketizer ::CONFIGURE_PACKET_RATES_cmdHandler(FwOpcodeType opCode, + U32 cmdSeq, + U32 packetId, + const Svc::TelemetrySection& section, + const Svc::RateLogic& rateLogic, + U32 minDelta, + U32 maxDelta) { + FwSizeType s = 0; + FwChanIdType pkt = 0; + if (not rateLogic.isValid() or not this->resolveAndSeedOverride(section, packetId, s, pkt)) { + this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::VALIDATION_ERROR); + return; + } + Svc::PacketConfig& cfg = this->m_packetOverride[s][pkt]; + cfg.set_rateLogic(rateLogic); + cfg.set_min(minDelta); + cfg.set_max(maxDelta); + this->mirrorOverride(section, pkt, packetId); + this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK); +} + void TlmPacketizer::configureSectionGroupRate( const Svc::TelemetrySection& section, //!< Section grouping FwChanIdType tlmGroup, //!< Group Identifier @@ -567,6 +646,100 @@ FwIndexType TlmPacketizer::sectionGroupToPort(const FwIndexType section, const F return outIndex; } +Svc::PacketConfig TlmPacketizer::effectiveConfig(FwIndexType section, FwChanIdType pkt, FwChanIdType group) const { + const FwSizeType s = static_cast(section); + if (this->m_packetOverridden[s][pkt]) { + return this->m_packetOverride[s][pkt]; + } + // Not overridden: derive from the group policy for this packet's level (legacy behavior). + const TlmPacketizer_GroupConfig& gc = this->m_groupConfigs[s][group]; + Svc::PacketConfig eff; + eff.set_enabled(gc.get_enabled()); + eff.set_forceEnabled(gc.get_forceEnabled()); + eff.set_rateLogic(gc.get_rateLogic()); + eff.set_min(gc.get_min()); + eff.set_max(gc.get_max()); + return eff; +} + +bool TlmPacketizer::findPacketIndexById(U32 packetId, FwChanIdType& pkt) const { + for (FwChanIdType p = 0; p < this->m_numPackets; p++) { + if (this->m_fillBuffers[p].id == packetId) { + pkt = p; + return true; + } + } + return false; +} + +Svc::PacketConfig TlmPacketizer::defaultPacketConfig() { + // seeds the default the first time the packet is overridden, assuming these parameters + // were not configured ahead of this packet being enabled: enabled, not forced, + // output-on-change, no thresholds. + Svc::PacketConfig cfg; + cfg.set_enabled(Fw::Enabled(Fw::Enabled::ENABLED)); + cfg.set_forceEnabled(Fw::Enabled(Fw::Enabled::DISABLED)); + cfg.set_rateLogic(Svc::RateLogic(Svc::RateLogic::ON_CHANGE_MIN)); + cfg.set_min(0); + cfg.set_max(0); + return cfg; +} + +bool TlmPacketizer::resolveAndSeedOverride(const Svc::TelemetrySection& section, + U32 packetId, + FwSizeType& s, + FwChanIdType& pkt) { + if (not(section.isValid() and section >= 0 and section < TelemetrySection::NUM_SECTIONS)) { + return false; + } + if (not this->findPacketIndexById(packetId, pkt)) { + this->log_WARNING_LO_UnknownPacketId(packetId); + return false; + } + s = static_cast(section.e); + if (not this->m_packetOverridden[s][pkt]) { + this->m_packetOverride[s][pkt] = TlmPacketizer::defaultPacketConfig(); + this->m_packetOverridden[s][pkt] = true; + } + return true; +} + +void TlmPacketizer ::configIn_handler(FwIndexType portNum, FwSizeType count, const Svc::PacketConfigBatch& batch) { + // load overrides from an external component's persistant storage of the overrides (intended to be used after a reboot) + const FwSizeType cap = static_cast(Svc::PacketConfigBatch::SIZE); + const FwSizeType n = (count < cap) ? count : cap; + for (FwSizeType i = 0; i < n; i++) { + const Svc::PacketConfigEntry& entry = batch[i]; + const Svc::TelemetrySection section = entry.get_section(); + if (not(section.isValid() and section >= 0 and section < TelemetrySection::NUM_SECTIONS)) { + continue; + } + FwChanIdType pkt = 0; + if (not this->findPacketIndexById(entry.get_packetId(), pkt)) { + this->log_WARNING_LO_UnknownPacketId(entry.get_packetId()); + continue; + } + const FwSizeType s = static_cast(section.e); + this->m_packetOverride[s][pkt] = entry.get_config(); + this->m_packetOverridden[s][pkt] = true; + } +} + +void TlmPacketizer::mirrorOverride(const Svc::TelemetrySection& section, FwChanIdType pkt, U32 packetId) { + // persistent storage component should mirror this state; skip if not wired + if (not this->isConnected_configOut_OutputPort(0)) { + return; + } + const FwSizeType s = static_cast(section.e); + Svc::PacketConfigEntry entry; + entry.set_packetId(packetId); + entry.set_section(section); + entry.set_config(this->m_packetOverride[s][pkt]); + Svc::PacketConfigBatch batch; + batch[0] = entry; + this->configOut_out(0, 1, batch); +} + void TlmPacketizer::missingChannel(FwChanIdType id) { // search to see if missing channel has already been sent for (FwChanIdType slot = 0; slot < TLMPACKETIZER_MAX_MISSING_TLM_CHECK; slot++) { diff --git a/Svc/TlmPacketizer/TlmPacketizer.fpp b/Svc/TlmPacketizer/TlmPacketizer.fpp index e7b88cad226..2188c235261 100644 --- a/Svc/TlmPacketizer/TlmPacketizer.fpp +++ b/Svc/TlmPacketizer/TlmPacketizer.fpp @@ -39,6 +39,15 @@ module Svc { @ Input configuration port async input port configureSectionGroupRate: ConfigureGroupRate + @ Per-packet configuration mirror to persistent storage managed by an external component + @ Each entry carries the full override for the addressed packet/section and is pushed + @ whenever an ENABLE_PACKET / FORCE_PACKET / CONFIGURE_PACKET_RATES command changes it. + output port configOut: TlmPacketConfigUpdate + + @ Per-packet configuration reload from the persistence storage component + @ That storage component pushes it's config into this port when commanded to do so (usually at boot-up) + async input port configIn: TlmPacketConfigUpdate + @ Telemetry input port sync input port TlmRecv: Fw.Tlm @@ -125,6 +134,41 @@ module Svc { maxDelta: U32 @< Maximum Sched Ticks between packets to send when using EVERY_MAX logic ) \ opcode 5 + + @ Query the effective per-packet configuration. The result is emitted on the + @ QueriedPacketConfig telemetry channel; unknown ids raise UnknownPacketId. + async command GET_PACKET_CONFIG( + packetId: U32 @< Packet identifier + section: TelemetrySection @< Section to query + ) \ + opcode 6 + + @ Enable / disable a single packet in a section (per-packet override) + async command ENABLE_PACKET( + packetId: U32 @< Packet identifier + section: TelemetrySection @< Section to configure + enable: Fw.Enabled @< Enable / disable this packet + ) \ + opcode 7 + + @ Force telemeter a single packet even when it (or its section) is disabled + async command FORCE_PACKET( + packetId: U32 @< Packet identifier + section: TelemetrySection @< Section to configure + enable: Fw.Enabled @< Force enable / disable + ) \ + opcode 8 + + @ Configure the rate logic and thresholds for a single packet + async command CONFIGURE_PACKET_RATES( + packetId: U32 @< Packet identifier + section: TelemetrySection @< Section to configure + rateLogic: RateLogic @< Rate logic + minDelta: U32 @< Minimum Sched ticks between sends (ON_CHANGE_MIN logic) + maxDelta: U32 @< Maximum Sched ticks between sends (EVERY_MAX logic) + ) \ + opcode 9 + @ Parameter to control section enable flags external param SECTION_ENABLED: SectionEnabled default TELEMETRY_SECTION_ENABLED_DEFAULTS @ Parameter to control section configuration @@ -194,6 +238,15 @@ module Svc { format "Telemetry ID 0x{x} update of size {} exceeds configured size {}" \ throttle 10 + + @ A configuration command or query referenced a packet id not present in this deployment + event UnknownPacketId( + packetId: U32 @< The packet id + ) \ + severity warning low \ + id 7 \ + format "Packet id {} not found in packet list" + # ---------------------------------------------------------------------- # Telemetry # ---------------------------------------------------------------------- @@ -202,6 +255,9 @@ module Svc { telemetry GroupConfigs: SectionConfigs id 0 telemetry SectionEnabled: SectionEnabled id 1 + @ Effective per-packet configuration, emitted in response to GET_PACKET_CONFIG + telemetry QueriedPacketConfig: PacketConfigEntry id 2 + array TelemetrySendSection = [NUM_CONFIGURABLE_TLMPACKETIZER_GROUPS] FwIndexType array TelemetrySendPortMap = [TelemetrySection.NUM_SECTIONS] TelemetrySendSection default TELEMETRY_SEND_PORT_MAPPING diff --git a/Svc/TlmPacketizer/TlmPacketizer.hpp b/Svc/TlmPacketizer/TlmPacketizer.hpp index f5ed26d2b70..a265feb2e38 100644 --- a/Svc/TlmPacketizer/TlmPacketizer.hpp +++ b/Svc/TlmPacketizer/TlmPacketizer.hpp @@ -93,6 +93,12 @@ class TlmPacketizer final : public TlmPacketizerComponentBase, public Fw::ParamE U32 key /*!< Value to return to pinger*/ ) override; + //! Handler for input port configIn: reload path. Applies a batch of persisted overrides + void configIn_handler(FwIndexType portNum, //!< The port number + FwSizeType count, //!< Number of valid entries in batch + const Svc::PacketConfigBatch& batch //!< Overrides to apply + ) override; + //! Handler for input port TlmGet Fw::TlmValid TlmGet_handler(FwIndexType portNum, //!< The port number FwChanIdType id, //!< Telemetry Channel ID @@ -152,6 +158,49 @@ class TlmPacketizer final : public TlmPacketizerComponentBase, public Fw::ParamE U32 maxDelta //!< Maximum Sched Ticks between packets to send when using EVERY_MAX logic ) override; + //! Handler implementation for command GET_PACKET_CONFIG + //! + //! Emits the effective per-packet configuration on QueriedPacketConfig. + void GET_PACKET_CONFIG_cmdHandler(FwOpcodeType opCode, //!< The opcode + U32 cmdSeq, //!< The command sequence number + U32 packetId, //!< Packet identifier + const Svc::TelemetrySection& section //!< Section to query + ) override; + + //! Handler implementation for command ENABLE_PACKET + //! + //! Enable / disable a single packet as a per-packet override, then mirror to configOut. + void ENABLE_PACKET_cmdHandler(FwOpcodeType opCode, //!< The opcode + U32 cmdSeq, //!< The command sequence number + U32 packetId, //!< Packet identifier + const Svc::TelemetrySection& section, //!< Section to configure + const Fw::Enabled& enable //!< Enable / disable this packet + ) override; + + //! Handler implementation for command FORCE_PACKET + //! + //! Force / unforce a single packet as a per-packet override, then mirror to configOut. + void FORCE_PACKET_cmdHandler(FwOpcodeType opCode, //!< The opcode + U32 cmdSeq, //!< The command sequence number + U32 packetId, //!< Packet identifier + const Svc::TelemetrySection& section, //!< Section to configure + const Fw::Enabled& enable //!< Force enable / disable + ) override; + + //! Handler implementation for command CONFIGURE_PACKET_RATES + //! + //! Set the rate logic / thresholds of a single packet as a per-packet override, then + //! mirror to configOut. + void CONFIGURE_PACKET_RATES_cmdHandler( + FwOpcodeType opCode, //!< The opcode + U32 cmdSeq, //!< The command sequence number + U32 packetId, //!< Packet identifier + const Svc::TelemetrySection& section, //!< Section to configure + const Svc::RateLogic& rateLogic, //!< Rate logic + U32 minDelta, //!< Minimum Sched ticks between sends when using ON_CHANGE_MIN logic + U32 maxDelta //!< Maximum Sched ticks between sends when using EVERY_MAX logic + ) override; + // number of packets to fill FwChanIdType m_numPackets; // Array of packet buffers to send @@ -193,6 +242,14 @@ class TlmPacketizer final : public TlmPacketizerComponentBase, public Fw::ParamE TlmPacketizer_SectionConfigs m_groupConfigs{}; + //! Per-packet policy overrides set by the ENABLE_PACKET / FORCE_PACKET / + //! CONFIGURE_PACKET_RATES commands. When m_packetOverridden is set, this value wins over + //! the group-derived policy for that packet/section. This is the per-packet control layer; + //! group config remains the base for non-overridden packets. Each change is mirrored out + //! configOut to the passive TlmPacketConfig for persistence. + Svc::PacketConfig m_packetOverride[TelemetrySection::NUM_SECTIONS][MAX_PACKETIZER_PACKETS]{}; + bool m_packetOverridden[TelemetrySection::NUM_SECTIONS][MAX_PACKETIZER_PACKETS]{}; + enum UpdateFlag : U8 { NEVER_UPDATED = 0, //!< Packet has never been updated (NO DATA) PAST = 1, //!< Packet has been sent and has old data @@ -239,6 +296,33 @@ class TlmPacketizer final : public TlmPacketizerComponentBase, public Fw::ParamE //! \return The output port index to send telemetry for the given section and group static FwIndexType sectionGroupToPort(const FwIndexType section, const FwSizeType group); + //! \brief Compute the effective per-packet policy for a packet/section. + //! + //! Returns the pushed override if one is set for this packet/section, otherwise the + //! group-derived policy for the packet's level (preserving legacy group behavior). + Svc::PacketConfig effectiveConfig(FwIndexType section, FwChanIdType pkt, FwChanIdType group) const; + + //! \brief Resolve a packet id to its index in the packet list. + //! \return true if found (index written to pkt), false otherwise. + bool findPacketIndexById(U32 packetId, FwChanIdType& pkt) const; + + //! \brief Behavior-preserving default per-packet policy (enabled, force disabled, + //! ON_CHANGE_MIN, 0/0) used to seed an override slot on its first use. + static Svc::PacketConfig defaultPacketConfig(); + + //! \brief Validate a per-packet command's section, resolve packetId to a packet index, and + //! ensure an override slot exists for (section, pkt) seeded from defaultPacketConfig() on + //! first use. Returns true on success (s/pkt written); false on an invalid section or an + //! unknown packet id (UnknownPacketId logged) so the caller responds VALIDATION_ERROR. + bool resolveAndSeedOverride(const Svc::TelemetrySection& section, + U32 packetId, + FwSizeType& s, + FwChanIdType& pkt); + + //! \brief Mirror the current override for (section, pkt) out configOut so the passive + //! TlmPacketConfig can hold the same volatile state and persist it on save. + void mirrorOverride(const Svc::TelemetrySection& section, FwChanIdType pkt, U32 packetId); + private: FwSizeType m_numChannels; //!< number of channels being packetized Fw::RedBlackTreeMap m_channelIndices; diff --git a/Svc/TlmPacketizer/test/ut/TlmPacketizerTester.cpp b/Svc/TlmPacketizer/test/ut/TlmPacketizerTester.cpp index e5442b429aa..6a1989d0ed4 100644 --- a/Svc/TlmPacketizer/test/ut/TlmPacketizerTester.cpp +++ b/Svc/TlmPacketizer/test/ut/TlmPacketizerTester.cpp @@ -1933,10 +1933,217 @@ void TlmPacketizerTester ::from_pingOut_handler(const FwIndexType portNum, U32 k this->pushFromPortEntry_pingOut(key); } +void TlmPacketizerTester ::from_configOut_handler(FwIndexType portNum, + FwSizeType count, + const Svc::PacketConfigBatch& batch) { + this->m_configOutInvokes++; + this->m_lastConfigCount = count; + this->m_lastConfigBatch = batch; +} + // ---------------------------------------------------------------------- // Helper methods // ---------------------------------------------------------------------- +void TlmPacketizerTester ::perPacketOverrideTest() { + this->stockConfiguration(); // all sections/groups enabled + this->component.setPacketList(packetList, ignore, 2); + + // Override: disable packet 1 (id = 4) in PRIMARY only via the per-packet command. Its + // SECONDARY copy and packet 2 remain group-enabled, so exactly one (packet, section) send + // should drop out. + this->sendCmd_ENABLE_PACKET(0, 0, 4, Svc::TelemetrySection::REALTIME, Fw::Enabled::DISABLED); + this->component.doDispatch(); + + // Populate both packets with data (same channels as sendPacketsTest) + Fw::Time ts; + Fw::TlmBuffer buff; + buff.resetSer(); + (void)buff.serializeFrom(static_cast(20)); + this->invoke_to_TlmRecv(0, 10, ts, buff); // first channel + buff.resetSer(); + (void)buff.serializeFrom(static_cast(15)); + this->invoke_to_TlmRecv(0, 100, ts, buff); // second channel + buff.resetSer(); + (void)buff.serializeFrom(static_cast(14)); + this->invoke_to_TlmRecv(0, 333, ts, buff); // third channel + buff.resetSer(); + (void)buff.serializeFrom(static_cast(1000000)); + this->invoke_to_TlmRecv(0, 13, ts, buff); // fifth channel + buff.resetSer(); + (void)buff.serializeFrom(static_cast(1010)); + this->invoke_to_TlmRecv(0, 250, ts, buff); // sixth channel + buff.resetSer(); + (void)buff.serializeFrom(static_cast(15)); + this->invoke_to_TlmRecv(0, 22, ts, buff); // seventh channel + + this->setTestTime(this->m_testTime); + this->invoke_to_Run(0, 0); + this->component.doDispatch(); + + // Baseline (all enabled) is 2 packets x NUM_SECTIONS; the override removes exactly one. + ASSERT_from_PktSend_SIZE(2 * Svc::TelemetrySection::NUM_SECTIONS - 1); +} + +void TlmPacketizerTester ::perPacketCommandsTest() { + this->stockConfiguration(); + this->component.setPacketList(packetList, ignore, 2); + + // ENABLE_PACKET on known id 4 / PRIMARY -> OK, exactly one mirror carrying the override + // seeded from the behavior-preserving default with only `enabled` changed. + this->clearHistory(); + this->m_configOutInvokes = 0; + this->sendCmd_ENABLE_PACKET(0, 10, 4, Svc::TelemetrySection::REALTIME, Fw::Enabled::DISABLED); + this->component.doDispatch(); + ASSERT_CMD_RESPONSE_SIZE(1); + ASSERT_CMD_RESPONSE(0, TlmPacketizer::OPCODE_ENABLE_PACKET, 10, Fw::CmdResponse::OK); + ASSERT_EQ(this->m_configOutInvokes, 1u); + ASSERT_EQ(static_cast(this->m_lastConfigCount), 1u); + { + const Svc::PacketConfigEntry& e = this->m_lastConfigBatch[0]; + ASSERT_EQ(e.get_packetId(), 4u); + ASSERT_EQ(e.get_section(), Svc::TelemetrySection::REALTIME); + const Svc::PacketConfig c = e.get_config(); + ASSERT_EQ(c.get_enabled(), Fw::Enabled::DISABLED); // set by the command + ASSERT_EQ(c.get_forceEnabled(), Fw::Enabled::DISABLED); // seeded default + ASSERT_EQ(c.get_rateLogic(), Svc::RateLogic::ON_CHANGE_MIN); // seeded default + } + + // FORCE_PACKET on the same id/section merges into the existing override. + this->clearHistory(); + this->m_configOutInvokes = 0; + this->sendCmd_FORCE_PACKET(0, 11, 4, Svc::TelemetrySection::REALTIME, Fw::Enabled::ENABLED); + this->component.doDispatch(); + ASSERT_CMD_RESPONSE(0, TlmPacketizer::OPCODE_FORCE_PACKET, 11, Fw::CmdResponse::OK); + ASSERT_EQ(this->m_configOutInvokes, 1u); + { + const Svc::PacketConfig c = this->m_lastConfigBatch[0].get_config(); + ASSERT_EQ(c.get_enabled(), Fw::Enabled::DISABLED); // preserved from ENABLE_PACKET + ASSERT_EQ(c.get_forceEnabled(), Fw::Enabled::ENABLED); // set by FORCE_PACKET + } + + // CONFIGURE_PACKET_RATES sets the rate fields on the same override. + this->clearHistory(); + this->m_configOutInvokes = 0; + this->sendCmd_CONFIGURE_PACKET_RATES(0, 12, 4, Svc::TelemetrySection::REALTIME, + Svc::RateLogic::EVERY_MAX, 5, 10); + this->component.doDispatch(); + ASSERT_CMD_RESPONSE(0, TlmPacketizer::OPCODE_CONFIGURE_PACKET_RATES, 12, Fw::CmdResponse::OK); + ASSERT_EQ(this->m_configOutInvokes, 1u); + { + const Svc::PacketConfig c = this->m_lastConfigBatch[0].get_config(); + ASSERT_EQ(c.get_rateLogic(), Svc::RateLogic::EVERY_MAX); + ASSERT_EQ(c.get_min(), 5u); + ASSERT_EQ(c.get_max(), 10u); + } + + // Unknown packet id -> VALIDATION_ERROR + UnknownPacketId warning, and no mirror. + this->clearHistory(); + this->m_configOutInvokes = 0; + this->sendCmd_ENABLE_PACKET(0, 13, 9999, Svc::TelemetrySection::REALTIME, Fw::Enabled::ENABLED); + this->component.doDispatch(); + ASSERT_CMD_RESPONSE(0, TlmPacketizer::OPCODE_ENABLE_PACKET, 13, Fw::CmdResponse::VALIDATION_ERROR); + ASSERT_EVENTS_UnknownPacketId_SIZE(1); + ASSERT_EVENTS_UnknownPacketId(0, 9999); + ASSERT_EQ(this->m_configOutInvokes, 0u); + + // Out-of-range section -> VALIDATION_ERROR, and no mirror. + this->clearHistory(); + this->m_configOutInvokes = 0; + this->sendCmd_FORCE_PACKET(0, 14, 4, Svc::TelemetrySection(Svc::TelemetrySection::NUM_SECTIONS), + Fw::Enabled::ENABLED); + this->component.doDispatch(); + ASSERT_CMD_RESPONSE(0, TlmPacketizer::OPCODE_FORCE_PACKET, 14, Fw::CmdResponse::VALIDATION_ERROR); + ASSERT_EQ(this->m_configOutInvokes, 0u); +} + +void TlmPacketizerTester ::getPacketConfigTest() { + this->stockConfiguration(); + this->component.setPacketList(packetList, ignore, 2); + this->clearHistory(); + + // Known packet id (8) in SECONDARY -> OK + one QueriedPacketConfig report + this->sendCmd_GET_PACKET_CONFIG(0, 0, 8, Svc::TelemetrySection::RECORDED); + this->component.doDispatch(); + ASSERT_CMD_RESPONSE_SIZE(1); + ASSERT_CMD_RESPONSE(0, TlmPacketizer::OPCODE_GET_PACKET_CONFIG, 0, Fw::CmdResponse::OK); + ASSERT_TLM_QueriedPacketConfig_SIZE(1); + + // Unknown packet id -> VALIDATION_ERROR + UnknownPacketId warning + this->clearHistory(); + this->sendCmd_GET_PACKET_CONFIG(0, 1, 9999, Svc::TelemetrySection::REALTIME); + this->component.doDispatch(); + ASSERT_CMD_RESPONSE(0, TlmPacketizer::OPCODE_GET_PACKET_CONFIG, 1, Fw::CmdResponse::VALIDATION_ERROR); + ASSERT_EVENTS_UnknownPacketId_SIZE(1); + ASSERT_EVENTS_UnknownPacketId(0, 9999); +} + +void TlmPacketizerTester ::configInReloadTest() { + this->stockConfiguration(); // all sections/groups enabled + this->component.setPacketList(packetList, ignore, 2); + + // Build a reload batch as TlmPacketConfig would on RELOAD_TO_PACKETIZER: disable packet + // id 4 in REALTIME, plus an unknown id that must be warned and skipped (not applied). + Svc::PacketConfig disabled; + disabled.set_enabled(Fw::Enabled(Fw::Enabled::DISABLED)); + disabled.set_forceEnabled(Fw::Enabled(Fw::Enabled::DISABLED)); + disabled.set_rateLogic(Svc::RateLogic(Svc::RateLogic::ON_CHANGE_MIN)); + disabled.set_min(0); + disabled.set_max(0); + + Svc::PacketConfigEntry known; + known.set_packetId(4); + known.set_section(Svc::TelemetrySection(Svc::TelemetrySection::REALTIME)); + known.set_config(disabled); + + Svc::PacketConfigEntry unknown; + unknown.set_packetId(9999); + unknown.set_section(Svc::TelemetrySection(Svc::TelemetrySection::REALTIME)); + unknown.set_config(disabled); + + Svc::PacketConfigBatch batch; + batch[0] = known; + batch[1] = unknown; + + this->clearHistory(); + this->m_configOutInvokes = 0; + this->invoke_to_configIn(0, 2, batch); + this->component.doDispatch(); + + // The unknown id is warned and skipped; the reload path never echoes back out configOut. + ASSERT_EVENTS_UnknownPacketId_SIZE(1); + ASSERT_EQ(this->m_configOutInvokes, 0u); + + // The known override took effect: populate both packets, Run, and confirm exactly one + // (packet, section) send dropped out relative to the all-enabled baseline. + Fw::Time ts; + Fw::TlmBuffer buff; + buff.resetSer(); + (void)buff.serializeFrom(static_cast(20)); + this->invoke_to_TlmRecv(0, 10, ts, buff); + buff.resetSer(); + (void)buff.serializeFrom(static_cast(15)); + this->invoke_to_TlmRecv(0, 100, ts, buff); + buff.resetSer(); + (void)buff.serializeFrom(static_cast(14)); + this->invoke_to_TlmRecv(0, 333, ts, buff); + buff.resetSer(); + (void)buff.serializeFrom(static_cast(1000000)); + this->invoke_to_TlmRecv(0, 13, ts, buff); + buff.resetSer(); + (void)buff.serializeFrom(static_cast(1010)); + this->invoke_to_TlmRecv(0, 250, ts, buff); + buff.resetSer(); + (void)buff.serializeFrom(static_cast(15)); + this->invoke_to_TlmRecv(0, 22, ts, buff); + + this->setTestTime(this->m_testTime); + this->invoke_to_Run(0, 0); + this->component.doDispatch(); + + ASSERT_from_PktSend_SIZE(2 * Svc::TelemetrySection::NUM_SECTIONS - 1); +} + void TlmPacketizerTester ::connectPorts() { // PktSend // this->component.set_PktSend_OutputPort(0, this->get_from_PktSend(0)); @@ -1990,6 +2197,12 @@ void TlmPacketizerTester ::connectPorts() { this->connect_to_controlIn(0, this->component.get_controlIn_InputPort(0)); this->connect_to_configureSectionGroupRate(0, this->component.get_configureSectionGroupRate_InputPort(0)); + + // configOut (per-packet override mirror to the passive TlmPacketConfig) + this->component.set_configOut_OutputPort(0, this->get_from_configOut(0)); + + // configIn (reload path: TlmPacketConfig pushes its persisted table back) + this->connect_to_configIn(0, this->component.get_configIn_InputPort(0)); } void TlmPacketizerTester::textLogIn(const FwEventIdType id, //!< The event ID diff --git a/Svc/TlmPacketizer/test/ut/TlmPacketizerTester.hpp b/Svc/TlmPacketizer/test/ut/TlmPacketizerTester.hpp index c631cbb08cc..55e6e4a1810 100644 --- a/Svc/TlmPacketizer/test/ut/TlmPacketizerTester.hpp +++ b/Svc/TlmPacketizer/test/ut/TlmPacketizerTester.hpp @@ -104,6 +104,21 @@ class TlmPacketizerTester : public TlmPacketizerGTestBase { //! Duplicate channel ID across packets with conflicting size asserts void duplicateChannelIdConflictingSizeTest(void); + //! Per-packet override test: an ENABLE_PACKET override disables one packet/section while + //! the group-enabled remainder still sends (per-packet control) + void perPacketOverrideTest(void); + + //! Per-packet command test: ENABLE_PACKET / FORCE_PACKET / CONFIGURE_PACKET_RATES update + //! the override table + mirror one entry out configOut; bad args/id -> VALIDATION_ERROR + void perPacketCommandsTest(void); + + //! configIn reload test: a batch pushed over configIn is applied to the volatile table + //! (unknown ids warned + skipped) and is not echoed back out configOut + void configInReloadTest(void); + + //! GET_PACKET_CONFIG test: effective config reported for a known id; unknown id warns + void getPacketConfigTest(void); + //! Oversized channel value is rejected with a warning event void oversizedChannelTest(void); @@ -129,6 +144,12 @@ class TlmPacketizerTester : public TlmPacketizerGTestBase { U32 key /*!< Value to return to pinger*/ ) override; + //! Handler for from_configOut: captures the per-packet override mirror sent to external component + void from_configOut_handler(FwIndexType portNum, //!< The port number + FwSizeType count, //!< Number of valid entries + const Svc::PacketConfigBatch& batch //!< The mirrored overrides + ) override; + virtual void textLogIn(const FwEventIdType id, /*!< The event ID*/ const Fw::Time& timeTag, /*!< The time*/ const Fw::LogSeverity severity, /*!< The severity*/ @@ -167,6 +188,11 @@ class TlmPacketizerTester : public TlmPacketizerGTestBase { // bool m_primaryTestLock{true}; //! Lock limited to entries from port 0 PktSend FwSizeType m_portOutInvokes[Svc::TELEMETRY_SEND_PORTS]{}; + + //! configOut mirror capture (per-packet override sent to an external component) + U32 m_configOutInvokes{0}; //!< Number of configOut invocations + FwSizeType m_lastConfigCount{0}; //!< Count arg of the most recent configOut invocation + Svc::PacketConfigBatch m_lastConfigBatch{}; //!< Batch of the most recent configOut invocation }; } // end namespace Svc diff --git a/Svc/TlmPacketizer/test/ut/main.cpp b/Svc/TlmPacketizer/test/ut/main.cpp index 1059be21eec..4408f15fb0d 100644 --- a/Svc/TlmPacketizer/test/ut/main.cpp +++ b/Svc/TlmPacketizer/test/ut/main.cpp @@ -114,6 +114,30 @@ TEST(TestNominal, sectionConfigParameterTest) { tester.sectionConfigParameterTest(); } +TEST(TestNominal, PerPacketOverrideTest) { + TEST_CASE(100.1.13, "Per-packet ENABLE_PACKET override disables a single packet/section"); + Svc::TlmPacketizerTester tester; + tester.perPacketOverrideTest(); +} + +TEST(TestNominal, PerPacketCommandsTest) { + TEST_CASE(100.1.15, "Per-packet commands update overrides + mirror out configOut"); + Svc::TlmPacketizerTester tester; + tester.perPacketCommandsTest(); +} + +TEST(TestNominal, ConfigInReloadTest) { + TEST_CASE(100.1.16, "configIn reload applies overrides to the volatile table without echo"); + Svc::TlmPacketizerTester tester; + tester.configInReloadTest(); +} + +TEST(TestNominal, GetPacketConfigTest) { + TEST_CASE(100.1.14, "GET_PACKET_CONFIG reports effective config; unknown id warns"); + Svc::TlmPacketizerTester tester; + tester.getPacketConfigTest(); +} + int main(int argc, char* argv[]) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); diff --git a/Svc/Types/TlmPacketizerTypes/TlmPacketizerTypes.fpp b/Svc/Types/TlmPacketizerTypes/TlmPacketizerTypes.fpp index 9eb77725149..b3128f04253 100644 --- a/Svc/Types/TlmPacketizerTypes/TlmPacketizerTypes.fpp +++ b/Svc/Types/TlmPacketizerTypes/TlmPacketizerTypes.fpp @@ -13,4 +13,15 @@ module Svc { ON_CHANGE_MIN, @< Send on updates after MIN ticks since last send. ON_CHANGE_MIN_OR_EVERY_MAX, @< Send on updates after MIN ticks since last send OR at MAX ticks between sends. } + + @ Per-packet telemetry policy: enable + rate configuration for a single packet. + @ Mirrors the fields of the legacy per-group configuration, but applied to one packet + @ so that each packet can be enabled/disabled and rate-controlled independently. + struct PacketConfig { + enabled: Fw.Enabled @< Enable / disable telemetry output for this packet + forceEnabled: Fw.Enabled @< Force telemetry output for this packet even if disabled + rateLogic: RateLogic @< Rate logic configuration + min: U32 @< Minimum Sched ticks between sends when using ON_CHANGE_MIN logic + max: U32 @< Maximum Sched ticks between sends when using EVERY_MAX logic + } }