G: CemObjectStore (E120 Common Equipment Model)
Hierarchical object tree for equipment self-description. Each object carries a CemObjectType (Equipment / Subsystem / IODevice / Module / MaterialLocation / Other), an optional parent_objid, and a flat attribute map keyed by name (the wire shape S14F1 / F3 returns). Operations covered: add(CemObject) - dedup'd, validates parent exists get / has - lookup by objid get_attr / set_attr - E14 GetAttr / SetAttr semantics children(parent) - tree traversal; empty parent = roots The flat-map representation matches how E14 ObjectService traffic addresses nodes (by OBJSPEC string). Wiring S14F1/F2 GetAttr and S14F3/F4 SetAttr to this store is a downstream commit; the data model is what was missing. Joins EquipmentDataModel alongside the other top-level stores. Three test cases cover hierarchical add+dedup, children() traversal, and get/set/missing attribute semantics. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "secsgem/gem/store/alarms.hpp"
|
||||
#include "secsgem/gem/store/carriers.hpp"
|
||||
#include "secsgem/gem/store/cem_objects.hpp"
|
||||
#include "secsgem/gem/store/clock.hpp"
|
||||
#include "secsgem/gem/ept_state.hpp"
|
||||
#include "secsgem/gem/store/control_jobs.hpp"
|
||||
@@ -43,6 +44,7 @@ struct EquipmentDataModel {
|
||||
LoadPortStore load_ports;
|
||||
SubstrateStore substrates;
|
||||
EptStateMachine ept;
|
||||
CemObjectStore cem;
|
||||
|
||||
// Convenience: VID -> value lookup spanning SVIDs and DVIDs.
|
||||
std::optional<s2::Item> vid_value(uint32_t vid) const {
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "secsgem/gem/carrier_state.hpp" // shared CarrierTransition template
|
||||
|
||||
// E157 §6 Module Process Tracking — per-module state model for a process
|
||||
// step within a recipe. The lifecycle is similar to E40 PJ but scoped
|
||||
// to a single module step rather than a whole job; an E157 instance is
|
||||
// typically created per (Module, Substrate, RecipeStep) triple by the
|
||||
// process executive when a wafer arrives at a module.
|
||||
//
|
||||
// Wire-byte values per E157-0712 §10.3.
|
||||
namespace secsgem::gem {
|
||||
|
||||
enum class ModuleState : uint8_t {
|
||||
NotExecuting = 0,
|
||||
GeneralExecuting = 1, // setup, pre-process, etc.
|
||||
StepExecuting = 2, // actively running the recipe step
|
||||
StepCompleted = 3,
|
||||
NoState = 255,
|
||||
};
|
||||
|
||||
const char* module_state_name(ModuleState s);
|
||||
std::optional<ModuleState> parse_module_state(const std::string& s);
|
||||
|
||||
enum class ModuleEvent {
|
||||
StartGeneral, // NotExecuting -> GeneralExecuting
|
||||
StartStep, // GeneralExecuting -> StepExecuting
|
||||
CompleteStep, // StepExecuting -> StepCompleted
|
||||
Reset, // any -> NotExecuting (cancellation)
|
||||
Abort, // any -> StepCompleted (early termination)
|
||||
};
|
||||
|
||||
const char* module_event_name(ModuleEvent e);
|
||||
|
||||
using ModuleTable = CarrierTransitionTable<ModuleState, ModuleEvent>;
|
||||
ModuleTable default_module_table();
|
||||
|
||||
class ModuleStateMachine {
|
||||
public:
|
||||
using StateChangeHandler =
|
||||
std::function<void(ModuleState from, ModuleState to, ModuleEvent)>;
|
||||
|
||||
ModuleStateMachine();
|
||||
|
||||
ModuleState state() const { return state_; }
|
||||
void set_state_change_handler(StateChangeHandler h) { on_change_ = std::move(h); }
|
||||
|
||||
bool on_event(ModuleEvent e);
|
||||
|
||||
private:
|
||||
ModuleTable table_;
|
||||
ModuleState state_ = ModuleState::NotExecuting;
|
||||
StateChangeHandler on_change_;
|
||||
};
|
||||
|
||||
} // namespace secsgem::gem
|
||||
@@ -0,0 +1,105 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "secsgem/secs2/item.hpp"
|
||||
|
||||
// E120 §6 Common Equipment Model. E120 defines a generic object
|
||||
// hierarchy (Equipment -> Subsystem -> IODevice / Module / Material
|
||||
// Location) for equipment self-description. Hosts query the tree via
|
||||
// the generic E14 Object Service messages (S14F1-F4 GetAttr / SetAttr).
|
||||
//
|
||||
// We model the hierarchy with a single CemObject node type whose
|
||||
// `objtype` is one of a small enum, parented by `parent_objid`, and
|
||||
// carrying a flat <name, Item> attribute map per E14. Real CEM trees
|
||||
// are rarely deeper than four levels; a flat map keyed by OBJSPEC
|
||||
// (parent.child.grand...) is the canonical traversal shape.
|
||||
namespace secsgem::gem {
|
||||
|
||||
namespace s2 = secsgem::secs2;
|
||||
|
||||
enum class CemObjectType : uint8_t {
|
||||
Equipment = 0, // top of the tree (one per tool)
|
||||
Subsystem = 1, // major functional unit
|
||||
IODevice = 2, // I/O device
|
||||
Module = 3, // process module (E157 ties in here)
|
||||
MaterialLoc = 4, // material location (load port, internal store)
|
||||
Other = 255,
|
||||
};
|
||||
|
||||
const char* cem_object_type_name(CemObjectType t);
|
||||
|
||||
struct CemObject {
|
||||
std::string objid; // unique within the equipment tree
|
||||
CemObjectType objtype;
|
||||
std::string parent_objid; // empty = root (Equipment)
|
||||
std::map<std::string, s2::Item> attributes;
|
||||
};
|
||||
|
||||
class CemObjectStore {
|
||||
public:
|
||||
enum class CreateResult { Created, Denied_AlreadyExists, Denied_BadParent };
|
||||
|
||||
CreateResult add(CemObject obj) {
|
||||
if (objs_.count(obj.objid)) return CreateResult::Denied_AlreadyExists;
|
||||
if (!obj.parent_objid.empty() && !objs_.count(obj.parent_objid))
|
||||
return CreateResult::Denied_BadParent;
|
||||
objs_.emplace(obj.objid, std::move(obj));
|
||||
return CreateResult::Created;
|
||||
}
|
||||
|
||||
bool has(const std::string& objid) const { return objs_.count(objid) > 0; }
|
||||
const CemObject* get(const std::string& objid) const {
|
||||
auto it = objs_.find(objid);
|
||||
return it == objs_.end() ? nullptr : &it->second;
|
||||
}
|
||||
CemObject* get(const std::string& objid) {
|
||||
auto it = objs_.find(objid);
|
||||
return it == objs_.end() ? nullptr : &it->second;
|
||||
}
|
||||
|
||||
// E14 GetAttr / SetAttr: returns nullopt for unknown object or attr.
|
||||
std::optional<s2::Item> get_attr(const std::string& objid,
|
||||
const std::string& name) const {
|
||||
const auto* o = get(objid);
|
||||
if (!o) return std::nullopt;
|
||||
auto it = o->attributes.find(name);
|
||||
if (it == o->attributes.end()) return std::nullopt;
|
||||
return it->second;
|
||||
}
|
||||
|
||||
bool set_attr(const std::string& objid, std::string name, s2::Item value) {
|
||||
auto* o = get(objid);
|
||||
if (!o) return false;
|
||||
o->attributes.insert_or_assign(std::move(name), std::move(value));
|
||||
return true;
|
||||
}
|
||||
|
||||
// Children of `parent_objid` (empty string returns roots).
|
||||
std::vector<const CemObject*> children(const std::string& parent_objid) const {
|
||||
std::vector<const CemObject*> out;
|
||||
for (const auto& kv : objs_) {
|
||||
if (kv.second.parent_objid == parent_objid) out.push_back(&kv.second);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
std::size_t size() const { return objs_.size(); }
|
||||
std::vector<std::string> ids() const {
|
||||
std::vector<std::string> out;
|
||||
out.reserve(objs_.size());
|
||||
for (const auto& kv : objs_) out.push_back(kv.first);
|
||||
return out;
|
||||
}
|
||||
|
||||
private:
|
||||
std::map<std::string, CemObject> objs_;
|
||||
};
|
||||
|
||||
} // namespace secsgem::gem
|
||||
Reference in New Issue
Block a user