e42: enhanced (formatted) process programs S7F23-F26

E42 was an explicit out-of-scope item in the prior COMPLIANCE.md.
This commit closes it.

Wire messages added via the catalog:
  S7F23  Formatted PP Send       (H↔E, W=1)
  S7F24  Formatted PP Ack        (ProcessProgramAck)
  S7F25  Formatted PP Request    (PPID, W=1)
  S7F26  Formatted PP Data       (E→H, no reply)

Body shape: <L,4 PPID MDLN SOFTREV <L,n <L,2 CCODE <L,m <L,2
PNAME PVAL>>>>>.  PVAL is declared ITEM so any SECS-II Item type
round-trips — proven by a test that mixes ASCII, BOOLEAN, U4, F8,
Binary, and nested List values in one step.

RecipeStore extension:
  add_formatted(ppid, FormattedRecipe{mdln, softrev, steps})
  get_formatted(ppid) -> optional<FormattedRecipe>
  has_formatted(ppid) -> bool

Formatted + opaque views live alongside each other: a PPID can carry
both, size() counts unique PPIDs.  remove() kills both views.

Six new tests cover wire round-trip per function, every
ProcessProgramAck code, ITEM passthrough, and the store's dual-view
semantics.

COMPLIANCE.md updated: E30 §6.17 row mentions S7F23-F26, S5 message
table grows two rows, §8 "out of scope" entry for E42 removed.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-06-09 11:58:03 +02:00
parent d4d1a411d7
commit 78fb0c3826
5 changed files with 317 additions and 8 deletions
+58 -6
View File
@@ -7,6 +7,8 @@
#include <utility>
#include <vector>
#include "secsgem/secs2/item.hpp"
namespace secsgem::gem {
enum class ProcessProgramAck : uint8_t {
@@ -19,6 +21,28 @@ enum class ProcessProgramAck : uint8_t {
PerformanceError = 6,
};
// One parameter on a formatted-PP step (E42). Name + arbitrary
// SECS-II Item value.
struct FormattedRecipeParam {
std::string name;
secs2::Item value;
};
// One step of a formatted PP: a CCODE (command name) and its
// parameter list. Matches the wire shape of S7F23/F26 row entries.
struct FormattedRecipeStep {
std::string ccode;
std::vector<FormattedRecipeParam> params;
};
// Equipment / model metadata that travels with a formatted PP. Set
// on add_formatted; round-tripped through S7F23/F26.
struct FormattedRecipe {
std::string mdln; // model name
std::string softrev; // software revision
std::vector<FormattedRecipeStep> steps;
};
class RecipeStore {
public:
void add(std::string ppid, std::string body) {
@@ -31,19 +55,47 @@ class RecipeStore {
}
std::vector<std::string> list() const {
std::vector<std::string> out;
out.reserve(by_ppid_.size());
out.reserve(by_ppid_.size() + formatted_.size());
for (const auto& [k, _] : by_ppid_) out.push_back(k);
for (const auto& [k, _] : formatted_) {
if (!by_ppid_.count(k)) out.push_back(k);
}
return out;
}
ProcessProgramAck remove(const std::string& ppid) {
if (!by_ppid_.count(ppid)) return ProcessProgramAck::PpidNotFound;
by_ppid_.erase(ppid);
return ProcessProgramAck::Accept;
bool found = (by_ppid_.erase(ppid) > 0) | (formatted_.erase(ppid) > 0);
return found ? ProcessProgramAck::Accept : ProcessProgramAck::PpidNotFound;
}
// Unique PPID count. A PPID with both opaque and formatted views
// counts once.
std::size_t size() const {
std::size_t n = by_ppid_.size();
for (const auto& [k, _] : formatted_) {
if (!by_ppid_.count(k)) ++n;
}
return n;
}
// ---- E42 formatted PPs ------------------------------------------------
// A formatted recipe lives alongside any opaque-body PP with the same
// PPID. Most tools pick one form or the other; storing both
// independently lets a host poke at the formatted view while a legacy
// app reads the opaque view.
void add_formatted(std::string ppid, FormattedRecipe r) {
formatted_.insert_or_assign(std::move(ppid), std::move(r));
}
std::optional<FormattedRecipe> get_formatted(const std::string& ppid) const {
auto it = formatted_.find(ppid);
if (it == formatted_.end()) return std::nullopt;
return it->second;
}
bool has_formatted(const std::string& ppid) const {
return formatted_.count(ppid) > 0;
}
std::size_t size() const { return by_ppid_.size(); }
private:
std::map<std::string, std::string> by_ppid_;
std::map<std::string, std::string> by_ppid_;
std::map<std::string, FormattedRecipe> formatted_;
};
} // namespace secsgem::gem