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:
2026-06-08 03:35:05 +02:00
parent cdf4049016
commit d159bd39d7
6 changed files with 234 additions and 0 deletions
+105
View File
@@ -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