#pragma once #include #include #include #include #include #include #include #include "secsgem/gem/handler_slot.hpp" #include "secsgem/secs2/item.hpp" namespace secsgem::gem { enum class ProcessProgramAck : uint8_t { Accept = 0, PermissionNotGranted = 1, LengthError = 2, MatrixOverflow = 3, PpidNotFound = 4, ModeUnsupported = 5, 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 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 steps; }; class RecipeStore { public: // Observe recipe arrivals (e.g. a host S7F3 download): fires after the // store is updated, with (ppid, body). Multi-observer via HandlerSlot — // the gRPC daemon forwards these onto its Subscribe stream. using AddedHandler = std::function; void add_added_handler(AddedHandler h) { on_added_.add(std::move(h)); } void add(std::string ppid, std::string body) { auto it = by_ppid_.insert_or_assign(std::move(ppid), std::move(body)).first; if (on_added_) on_added_(it->first, it->second); } std::optional get(const std::string& ppid) const { auto it = by_ppid_.find(ppid); if (it == by_ppid_.end()) return std::nullopt; return it->second; } std::vector list() const { std::vector out; 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) { 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 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; } private: std::map by_ppid_; HandlerSlot on_added_; std::map formatted_; }; } // namespace secsgem::gem