BB1: full E40 S16F11 body — MF, PRRECIPEMETHOD, RCPVARS, PRPROCESSPARAMS
Replaces the simplified <L,3 PRJOBID PPID MTRLOUTSPEC> demo body with
the full SEMI E40-0705 §10.2 shape:
<L,5 PRJOBID MF PRRECIPEMETHOD
<L,2 PPID <L,n <L,2 RCPPARNM RCPPARVAL>>>
<L,n MTRLOUTSPEC>
<L,n <L,2 PARAMNAME PARAMVAL>>>
ProcessJob now carries the extra fields (MaterialFlag, ProcessRecipeMethod,
RcpVar[], ProcessParam[]) so a tool's recipe engine can later consume
the recipe-variable overrides and per-job process parameters. Server
S16F11 dispatch populates them via the new ProcessJobStore::set_e40_extras
helper after a successful create.
MaterialFlag + ProcessRecipeMethod enums live in their own tiny header
(`e40_constants.hpp`) so process_jobs.hpp (the store) can use them
without dragging in messages_helpers.hpp (which would create a circular
include via data_model.hpp).
The simplified 3-arg HostHandler::send_create_process_job convenience
remains; it constructs a sensible-default PRJobCreateRequest internally.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -409,9 +409,15 @@ int main(int argc, char** argv) {
|
|||||||
// ---- E40/E94: create a PJ, wrap it in a CJ, start the CJ ----------
|
// ---- E40/E94: create a PJ, wrap it in a CJ, start the CJ ----------
|
||||||
|
|
||||||
// 14a. S16F11 PRJobCreate PJ-1 with recipe RECIPE-A and 2 wafers.
|
// 14a. S16F11 PRJobCreate PJ-1 with recipe RECIPE-A and 2 wafers.
|
||||||
|
// Uses the full E40-0705 body (MF=Substrate, RecipeOnly, no extras).
|
||||||
seq->steps.push_back([conn, logfn, fail](auto next) {
|
seq->steps.push_back([conn, logfn, fail](auto next) {
|
||||||
|
gem::PRJobCreateRequest pj_req{
|
||||||
|
"PJ-1", gem::MaterialFlag::Substrate,
|
||||||
|
gem::ProcessRecipeMethod::RecipeOnly,
|
||||||
|
gem::RecipeSpec{"RECIPE-A", {}},
|
||||||
|
{"WFR-1", "WFR-2"}, {}};
|
||||||
conn->send_request(
|
conn->send_request(
|
||||||
gem::s16f11_pr_job_create("PJ-1", "RECIPE-A", {"WFR-1", "WFR-2"}),
|
gem::s16f11_pr_job_create(pj_req),
|
||||||
[logfn, fail, next](std::error_code ec, const s2::Message& reply) {
|
[logfn, fail, next](std::error_code ec, const s2::Message& reply) {
|
||||||
if (ec) { fail("S16F11", ec); return; }
|
if (ec) { fail("S16F11", ec); return; }
|
||||||
auto a = gem::ack_byte(reply);
|
auto a = gem::ack_byte(reply);
|
||||||
|
|||||||
+20
-2
@@ -1049,7 +1049,7 @@ int main(int argc, char** argv) {
|
|||||||
auto req = gem::parse_s16f11(msg);
|
auto req = gem::parse_s16f11(msg);
|
||||||
if (!req) return gem::s16f12_pr_job_create_ack(gem::HostCmdAck::ParameterInvalid);
|
if (!req) return gem::s16f12_pr_job_create_ack(gem::HostCmdAck::ParameterInvalid);
|
||||||
auto r = model->process_jobs.create(
|
auto r = model->process_jobs.create(
|
||||||
req->prjobid, req->ppid, req->mtrloutspec,
|
req->prjobid, req->rcpspec.ppid, req->mtrloutspec,
|
||||||
[model](const std::string& ppid) {
|
[model](const std::string& ppid) {
|
||||||
return model->recipes.get(ppid).has_value();
|
return model->recipes.get(ppid).has_value();
|
||||||
});
|
});
|
||||||
@@ -1061,7 +1061,25 @@ int main(int argc, char** argv) {
|
|||||||
case gem::ProcessJobStore::CreateResult::Denied_InvalidPpid:
|
case gem::ProcessJobStore::CreateResult::Denied_InvalidPpid:
|
||||||
ack = gem::HostCmdAck::ParameterInvalid; break;
|
ack = gem::HostCmdAck::ParameterInvalid; break;
|
||||||
}
|
}
|
||||||
logfn("S16F11 PJ=" + req->prjobid + " PPID=" + req->ppid +
|
if (ack == gem::HostCmdAck::Accept) {
|
||||||
|
// Persist the optional E40-0705 trailers (MF / recipe-method /
|
||||||
|
// recipe variables / process parameters) on the freshly created PJ.
|
||||||
|
std::vector<gem::RcpVar> rcpvars;
|
||||||
|
rcpvars.reserve(req->rcpspec.rcpvars.size());
|
||||||
|
for (auto& v : req->rcpspec.rcpvars) rcpvars.push_back({v.name, v.value});
|
||||||
|
std::vector<gem::ProcessParam> params;
|
||||||
|
params.reserve(req->prprocessparams.size());
|
||||||
|
for (auto& p : req->prprocessparams) params.push_back({p.name, p.value});
|
||||||
|
model->process_jobs.set_e40_extras(req->prjobid, req->mf,
|
||||||
|
req->prrecipemethod,
|
||||||
|
std::move(rcpvars),
|
||||||
|
std::move(params));
|
||||||
|
}
|
||||||
|
logfn("S16F11 PJ=" + req->prjobid + " PPID=" + req->rcpspec.ppid +
|
||||||
|
" MF=" + std::to_string(static_cast<int>(req->mf)) +
|
||||||
|
" RM=" + std::to_string(static_cast<int>(req->prrecipemethod)) +
|
||||||
|
" rcpvars=" + std::to_string(req->rcpspec.rcpvars.size()) +
|
||||||
|
" params=" + std::to_string(req->prprocessparams.size()) +
|
||||||
" -> S16F12 HCACK=" + std::to_string(static_cast<int>(ack)));
|
" -> S16F12 HCACK=" + std::to_string(static_cast<int>(ack)));
|
||||||
return gem::s16f12_pr_job_create_ack(ack);
|
return gem::s16f12_pr_job_create_ack(ack);
|
||||||
});
|
});
|
||||||
|
|||||||
+35
-7
@@ -2019,14 +2019,16 @@ messages:
|
|||||||
- {name: prjobid, shape: {kind: scalar, item_type: ASCII}}
|
- {name: prjobid, shape: {kind: scalar, item_type: ASCII}}
|
||||||
- {name: prjobstate, shape: {kind: scalar, item_type: BINARY_BYTE, enum: ProcessJobState}}
|
- {name: prjobstate, shape: {kind: scalar, item_type: BINARY_BYTE, enum: ProcessJobState}}
|
||||||
|
|
||||||
# S16F11 / F12 — PRJobCreate.
|
# S16F11 / F12 — PRJobCreate. Full E40-0705 §10.2 body:
|
||||||
#
|
#
|
||||||
# Real E40-0705 S16F11 body is <L,5 PRJOBID MF PRRECIPEMETHOD
|
# <L,5 PRJOBID MF PRRECIPEMETHOD RCPSPEC <L,n MTRLOUTSPEC> <L,n PRPROCESSPARAMS>>
|
||||||
# RCPSPEC L MTRLOUTSPEC L PRPROCESSPARAMS>. We simplify to the
|
# where RCPSPEC = <L,2 PPID <L,n <L,2 RCPPARNM RCPPARVAL>>>
|
||||||
# three pieces that actually drive the demo state machine:
|
# PRPROCESSPARAMS entry = <L,2 PARAMNAME PARAMVAL>
|
||||||
# PRJOBID, recipe (PPID), and the list of material identifiers. MF
|
#
|
||||||
# / PRRECIPEMETHOD / PRPROCESSPARAMS are tool-specific; layering
|
# The earlier simplified 3-field form (PRJOBID, PPID, MTRLOUTSPEC) is
|
||||||
# them in is a YAML edit + builder overload, not surgery.
|
# still available via a hand-written convenience overload
|
||||||
|
# `s16f11_pr_job_create_simple` for callers that don't care about
|
||||||
|
# MF / PRRECIPEMETHOD / process params.
|
||||||
- id: S16F11
|
- id: S16F11
|
||||||
stream: 16
|
stream: 16
|
||||||
function: 11
|
function: 11
|
||||||
@@ -2038,9 +2040,35 @@ messages:
|
|||||||
struct_name: PRJobCreateRequest
|
struct_name: PRJobCreateRequest
|
||||||
fields:
|
fields:
|
||||||
- {name: prjobid, shape: {kind: scalar, item_type: ASCII}}
|
- {name: prjobid, shape: {kind: scalar, item_type: ASCII}}
|
||||||
|
- {name: mf, shape: {kind: scalar, item_type: BINARY_BYTE, enum: MaterialFlag}}
|
||||||
|
- {name: prrecipemethod,
|
||||||
|
shape: {kind: scalar, item_type: BINARY_BYTE, enum: ProcessRecipeMethod}}
|
||||||
|
- name: rcpspec
|
||||||
|
shape:
|
||||||
|
kind: list
|
||||||
|
struct_name: RecipeSpec
|
||||||
|
fields:
|
||||||
- {name: ppid, shape: {kind: scalar, item_type: ASCII}}
|
- {name: ppid, shape: {kind: scalar, item_type: ASCII}}
|
||||||
|
- name: rcpvars
|
||||||
|
shape:
|
||||||
|
kind: list_of
|
||||||
|
element:
|
||||||
|
kind: list
|
||||||
|
struct_name: RecipeVariable
|
||||||
|
fields:
|
||||||
|
- {name: name, shape: {kind: scalar, item_type: ASCII}}
|
||||||
|
- {name: value, shape: {kind: scalar, item_type: ITEM}}
|
||||||
- name: mtrloutspec
|
- name: mtrloutspec
|
||||||
shape: {kind: list_of, element: {kind: scalar, item_type: ASCII}}
|
shape: {kind: list_of, element: {kind: scalar, item_type: ASCII}}
|
||||||
|
- name: prprocessparams
|
||||||
|
shape:
|
||||||
|
kind: list_of
|
||||||
|
element:
|
||||||
|
kind: list
|
||||||
|
struct_name: ProcessParameter
|
||||||
|
fields:
|
||||||
|
- {name: name, shape: {kind: scalar, item_type: ASCII}}
|
||||||
|
- {name: value, shape: {kind: scalar, item_type: ITEM}}
|
||||||
|
|
||||||
- id: S16F12
|
- id: S16F12
|
||||||
stream: 16
|
stream: 16
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
// E40-0705 §10.2 constants used by both the runtime stores (e.g.
|
||||||
|
// ProcessJob carries MaterialFlag) and the generated message catalog
|
||||||
|
// (S16F11 PRJobCreate body shape). Kept in its own tiny header so the
|
||||||
|
// store side can use them without pulling in messages_helpers.hpp,
|
||||||
|
// which in turn pulls in data_model.hpp -> the stores themselves.
|
||||||
|
|
||||||
|
namespace secsgem::gem {
|
||||||
|
|
||||||
|
// E40 S16F11 MF (Material Flag). Classifies what the material outspec
|
||||||
|
// represents. Values from SEMI E40 §10.2.
|
||||||
|
enum class MaterialFlag : uint8_t {
|
||||||
|
Substrate = 0, // outspec holds substrate / wafer IDs
|
||||||
|
Carrier = 1, // outspec holds carrier IDs
|
||||||
|
Other = 2, // tool-defined
|
||||||
|
};
|
||||||
|
|
||||||
|
// E40 S16F11 PRRECIPEMETHOD. Tells equipment what to do with the
|
||||||
|
// recipe id in RCPSPEC.
|
||||||
|
enum class ProcessRecipeMethod : uint8_t {
|
||||||
|
RecipeOnly = 0, // PPID identifies an existing recipe; use as is
|
||||||
|
RecipeWithVariableTuning = 1, // PPID + apply RCPVARLIST overrides
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace secsgem::gem
|
||||||
@@ -8,6 +8,7 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "secsgem/gem/data_model.hpp"
|
#include "secsgem/gem/data_model.hpp"
|
||||||
|
#include "secsgem/gem/e40_constants.hpp"
|
||||||
#include "secsgem/secs2/item.hpp"
|
#include "secsgem/secs2/item.hpp"
|
||||||
#include "secsgem/secs2/message.hpp"
|
#include "secsgem/secs2/message.hpp"
|
||||||
|
|
||||||
|
|||||||
@@ -8,18 +8,39 @@
|
|||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "secsgem/gem/e40_constants.hpp"
|
||||||
#include "secsgem/gem/process_job_state.hpp"
|
#include "secsgem/gem/process_job_state.hpp"
|
||||||
|
#include "secsgem/secs2/item.hpp"
|
||||||
|
|
||||||
namespace secsgem::gem {
|
namespace secsgem::gem {
|
||||||
|
|
||||||
// One Process Job record. The FSM is heap-allocated through unique_ptr so
|
// One Process Job record. The FSM is heap-allocated through unique_ptr so
|
||||||
// the per-PJ state-change handler can capture a stable pointer to
|
// the per-PJ state-change handler can capture a stable pointer to
|
||||||
// `this`-style state without invalidation on map rehash.
|
// `this`-style state without invalidation on map rehash.
|
||||||
|
//
|
||||||
|
// The MF / recipe-method / rcp-vars / process-params fields are the
|
||||||
|
// optional E40-0705 §10.2 trailers on S16F11. Simple callers leave
|
||||||
|
// them defaulted; tools that actually need recipe-variable tuning or
|
||||||
|
// per-job process parameters populate them.
|
||||||
|
struct RcpVar {
|
||||||
|
std::string name;
|
||||||
|
secs2::Item value;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ProcessParam {
|
||||||
|
std::string name;
|
||||||
|
secs2::Item value;
|
||||||
|
};
|
||||||
|
|
||||||
struct ProcessJob {
|
struct ProcessJob {
|
||||||
std::string prjobid;
|
std::string prjobid;
|
||||||
std::string ppid; // recipe identifier
|
std::string ppid; // recipe identifier
|
||||||
std::vector<std::string> mtrloutspec; // material identifiers
|
std::vector<std::string> mtrloutspec; // material identifiers
|
||||||
bool alert_enabled = true; // S16F9 alerts on/off
|
bool alert_enabled = true; // S16F9 alerts on/off
|
||||||
|
MaterialFlag mf = MaterialFlag::Substrate;
|
||||||
|
ProcessRecipeMethod prrecipemethod = ProcessRecipeMethod::RecipeOnly;
|
||||||
|
std::vector<RcpVar> rcpvars;
|
||||||
|
std::vector<ProcessParam> prprocessparams;
|
||||||
std::unique_ptr<ProcessJobStateMachine> fsm;
|
std::unique_ptr<ProcessJobStateMachine> fsm;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -71,9 +92,13 @@ class ProcessJobStore {
|
|||||||
if (on_change_) on_change_(id, from, to, trig);
|
if (on_change_) on_change_(id, from, to, trig);
|
||||||
});
|
});
|
||||||
order_.push_back(prjobid);
|
order_.push_back(prjobid);
|
||||||
jobs_.emplace(prjobid, ProcessJob{prjobid, std::move(ppid),
|
ProcessJob pj;
|
||||||
std::move(materials), true,
|
pj.prjobid = prjobid;
|
||||||
std::move(fsm)});
|
pj.ppid = std::move(ppid);
|
||||||
|
pj.mtrloutspec = std::move(materials);
|
||||||
|
pj.alert_enabled = true;
|
||||||
|
pj.fsm = std::move(fsm);
|
||||||
|
jobs_.emplace(prjobid, std::move(pj));
|
||||||
// Synthetic NoState -> Queued so subscribers observe creation. The
|
// Synthetic NoState -> Queued so subscribers observe creation. The
|
||||||
// server filters this so it doesn't emit a bogus S16F9 for a PJ
|
// server filters this so it doesn't emit a bogus S16F9 for a PJ
|
||||||
// that's still being acked.
|
// that's still being acked.
|
||||||
@@ -84,6 +109,23 @@ class ProcessJobStore {
|
|||||||
return CreateResult::Created;
|
return CreateResult::Created;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// After `create`, populate the optional E40-0705 fields on the new PJ.
|
||||||
|
// Returns true if the PJ exists. These fields don't influence the FSM;
|
||||||
|
// they're carried so the tool's recipe engine can read them later.
|
||||||
|
bool set_e40_extras(const std::string& prjobid,
|
||||||
|
MaterialFlag mf,
|
||||||
|
ProcessRecipeMethod prrecipemethod,
|
||||||
|
std::vector<RcpVar> rcpvars,
|
||||||
|
std::vector<ProcessParam> params) {
|
||||||
|
auto it = jobs_.find(prjobid);
|
||||||
|
if (it == jobs_.end()) return false;
|
||||||
|
it->second.mf = mf;
|
||||||
|
it->second.prrecipemethod = prrecipemethod;
|
||||||
|
it->second.rcpvars = std::move(rcpvars);
|
||||||
|
it->second.prprocessparams = std::move(params);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool has(const std::string& prjobid) const {
|
bool has(const std::string& prjobid) const {
|
||||||
return jobs_.count(prjobid) > 0;
|
return jobs_.count(prjobid) > 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -218,7 +218,17 @@ void HostHandler::send_terminal_display_multi(uint32_t tid,
|
|||||||
void HostHandler::send_create_process_job(
|
void HostHandler::send_create_process_job(
|
||||||
const std::string& prjobid, const std::string& ppid,
|
const std::string& prjobid, const std::string& ppid,
|
||||||
const std::vector<std::string>& mtrloutspec, ReplyHandler cb) {
|
const std::vector<std::string>& mtrloutspec, ReplyHandler cb) {
|
||||||
send_request(s16f11_pr_job_create(prjobid, ppid, mtrloutspec), std::move(cb));
|
// Simplified caller (no MF / process params / recipe-vars). Fill in
|
||||||
|
// sensible E40 defaults: substrate-flow, recipe-only (no var tuning),
|
||||||
|
// empty rcpvars + params.
|
||||||
|
PRJobCreateRequest req{
|
||||||
|
prjobid,
|
||||||
|
MaterialFlag::Substrate,
|
||||||
|
ProcessRecipeMethod::RecipeOnly,
|
||||||
|
RecipeSpec{ppid, {}},
|
||||||
|
mtrloutspec,
|
||||||
|
{}};
|
||||||
|
send_request(s16f11_pr_job_create(req), std::move(cb));
|
||||||
}
|
}
|
||||||
|
|
||||||
void HostHandler::send_pr_job_command(const std::string& prjobid,
|
void HostHandler::send_pr_job_command(const std::string& prjobid,
|
||||||
|
|||||||
+21
-6
@@ -782,16 +782,31 @@ TEST_CASE("S14F11 / S14F12 DeleteControlJob round-trip") {
|
|||||||
CHECK(*ack_byte(ack) == 2);
|
CHECK(*ack_byte(ack) == 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("S16F11 / S16F12 PRJobCreate round-trip") {
|
TEST_CASE("S16F11 / S16F12 PRJobCreate round-trip (full E40 body)") {
|
||||||
auto req = s16f11_pr_job_create("PJ-1", "RECIPE-A", {"WFR-1", "WFR-2"});
|
PRJobCreateRequest req_struct{
|
||||||
CHECK(req.stream == 16);
|
"PJ-1",
|
||||||
CHECK(req.function == 11);
|
MaterialFlag::Substrate,
|
||||||
|
ProcessRecipeMethod::RecipeWithVariableTuning,
|
||||||
|
RecipeSpec{"RECIPE-A",
|
||||||
|
{RecipeVariable{"TEMP", s2::Item::u4(450)},
|
||||||
|
RecipeVariable{"TIME", s2::Item::u4(60)}}},
|
||||||
|
{"WFR-1", "WFR-2"},
|
||||||
|
{ProcessParameter{"LotID", s2::Item::ascii("LOT-42")}}};
|
||||||
|
auto m = s16f11_pr_job_create(req_struct);
|
||||||
|
CHECK(m.stream == 16);
|
||||||
|
CHECK(m.function == 11);
|
||||||
|
|
||||||
auto parsed = parse_s16f11(req);
|
auto parsed = parse_s16f11(m);
|
||||||
REQUIRE(parsed.has_value());
|
REQUIRE(parsed.has_value());
|
||||||
CHECK(parsed->prjobid == "PJ-1");
|
CHECK(parsed->prjobid == "PJ-1");
|
||||||
CHECK(parsed->ppid == "RECIPE-A");
|
CHECK(parsed->mf == MaterialFlag::Substrate);
|
||||||
|
CHECK(parsed->prrecipemethod == ProcessRecipeMethod::RecipeWithVariableTuning);
|
||||||
|
CHECK(parsed->rcpspec.ppid == "RECIPE-A");
|
||||||
|
REQUIRE(parsed->rcpspec.rcpvars.size() == 2);
|
||||||
|
CHECK(parsed->rcpspec.rcpvars[0].name == "TEMP");
|
||||||
CHECK(parsed->mtrloutspec == std::vector<std::string>{"WFR-1", "WFR-2"});
|
CHECK(parsed->mtrloutspec == std::vector<std::string>{"WFR-1", "WFR-2"});
|
||||||
|
REQUIRE(parsed->prprocessparams.size() == 1);
|
||||||
|
CHECK(parsed->prprocessparams[0].name == "LotID");
|
||||||
|
|
||||||
CHECK(*ack_byte(s16f12_pr_job_create_ack(HostCmdAck::Accept)) == 0);
|
CHECK(*ack_byte(s16f12_pr_job_create_ack(HostCmdAck::Accept)) == 0);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user