M: S16F7/F8 PRJobMonitor + S16F15/F16 PRJobCreateMultiple
Closes the two E40 bulk/control gaps the COMPLIANCE doc had flagged
as out-of-scope:
S16F7 / F8 PRJobMonitor — host enables/disables S16F9 alerts
per PJ. PRALERT bit 7 is the enable flag (matches the
ALED convention from S5F3). Server dispatches into the
existing set_alert() store API.
S16F15 / F16 PRJobCreateMultiple — bulk create variant. Host posts
a list of (PRJOBID, PPID, MTRLOUTSPEC) entries; the
equipment processes them in order and returns a
per-PJ HCACK list so the host can identify which
subset failed. Same validators as S16F11.
Catalog now has three new structs: PRJobMonitorEntry,
PRJobCreateEntry, PRJobCreateMultiResult. Two round-trip tests cover
the new wire shapes; server-side correctness is exercised through the
existing PJ store invariants (dedup, validator) which both new paths
delegate to.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -886,6 +886,45 @@ int main(int argc, char** argv) {
|
|||||||
" -> S16F14 HCACK=" + std::to_string(static_cast<int>(ack)));
|
" -> S16F14 HCACK=" + std::to_string(static_cast<int>(ack)));
|
||||||
return gem::s16f14_pr_job_dequeue_ack(ack);
|
return gem::s16f14_pr_job_dequeue_ack(ack);
|
||||||
});
|
});
|
||||||
|
router.on(16, 7, [model, logfn](const s2::Message& msg) {
|
||||||
|
auto req = gem::parse_s16f7(msg);
|
||||||
|
if (!req) return gem::s16f8_pr_job_monitor_ack(gem::HostCmdAck::ParameterInvalid);
|
||||||
|
bool any_bad = false;
|
||||||
|
for (const auto& e : req->entries) {
|
||||||
|
const bool enable = (e.pralert & 0x80) != 0;
|
||||||
|
if (!model->process_jobs.set_alert(e.prjobid, enable)) any_bad = true;
|
||||||
|
}
|
||||||
|
const auto ack = any_bad ? gem::HostCmdAck::InvalidObject : gem::HostCmdAck::Accept;
|
||||||
|
logfn("S16F7 monitor " + std::to_string(req->entries.size()) +
|
||||||
|
" jobs -> S16F8 HCACK=" + std::to_string(static_cast<int>(ack)));
|
||||||
|
return gem::s16f8_pr_job_monitor_ack(ack);
|
||||||
|
});
|
||||||
|
router.on(16, 15, [model, logfn](const s2::Message& msg) {
|
||||||
|
auto req = gem::parse_s16f15(msg);
|
||||||
|
if (!req)
|
||||||
|
return gem::s16f16_pr_job_create_multi_ack(std::vector<gem::PRJobCreateMultiResult>{});
|
||||||
|
std::vector<gem::PRJobCreateMultiResult> results;
|
||||||
|
results.reserve(req->jobs.size());
|
||||||
|
for (const auto& job : req->jobs) {
|
||||||
|
auto r = model->process_jobs.create(
|
||||||
|
job.prjobid, job.ppid, job.mtrloutspec,
|
||||||
|
[model](const std::string& ppid) {
|
||||||
|
return model->recipes.get(ppid).has_value();
|
||||||
|
});
|
||||||
|
gem::HostCmdAck ack = gem::HostCmdAck::Accept;
|
||||||
|
switch (r) {
|
||||||
|
case gem::ProcessJobStore::CreateResult::Created: break;
|
||||||
|
case gem::ProcessJobStore::CreateResult::Denied_AlreadyExists:
|
||||||
|
ack = gem::HostCmdAck::Rejected; break;
|
||||||
|
case gem::ProcessJobStore::CreateResult::Denied_InvalidPpid:
|
||||||
|
ack = gem::HostCmdAck::ParameterInvalid; break;
|
||||||
|
}
|
||||||
|
results.push_back({job.prjobid, ack});
|
||||||
|
}
|
||||||
|
logfn("S16F15 multi-create " + std::to_string(req->jobs.size()) +
|
||||||
|
" jobs -> S16F16");
|
||||||
|
return gem::s16f16_pr_job_create_multi_ack(results);
|
||||||
|
});
|
||||||
router.on(16, 5, [model, logfn](const s2::Message& msg) {
|
router.on(16, 5, [model, logfn](const s2::Message& msg) {
|
||||||
auto req = gem::parse_s16f5(msg);
|
auto req = gem::parse_s16f5(msg);
|
||||||
if (!req) return gem::s16f6_pr_job_command_ack(gem::HostCmdAck::ParameterInvalid);
|
if (!req) return gem::s16f6_pr_job_command_ack(gem::HostCmdAck::ParameterInvalid);
|
||||||
|
|||||||
@@ -1541,6 +1541,80 @@ messages:
|
|||||||
builder: s16f14_pr_job_dequeue_ack
|
builder: s16f14_pr_job_dequeue_ack
|
||||||
body: {kind: scalar, item_type: BINARY_BYTE, enum: HostCmdAck, param: ack}
|
body: {kind: scalar, item_type: BINARY_BYTE, enum: HostCmdAck, param: ack}
|
||||||
|
|
||||||
|
# S16F7 / S16F8 — PRJobMonitor. Host enables or disables S16F9
|
||||||
|
# PRJobAlert emission per PRJOBID. PRALERT is a 1-byte boolean
|
||||||
|
# (0 = disable, 0x80 = enable, per E40-0705 §10).
|
||||||
|
- id: S16F7
|
||||||
|
stream: 16
|
||||||
|
function: 7
|
||||||
|
w: true
|
||||||
|
builder: s16f7_pr_job_monitor
|
||||||
|
parser: parse_s16f7
|
||||||
|
body:
|
||||||
|
kind: list
|
||||||
|
struct_name: PRJobMonitorRequest
|
||||||
|
fields:
|
||||||
|
- name: entries
|
||||||
|
shape:
|
||||||
|
kind: list_of
|
||||||
|
element:
|
||||||
|
kind: list
|
||||||
|
struct_name: PRJobMonitorEntry
|
||||||
|
fields:
|
||||||
|
- {name: prjobid, shape: {kind: scalar, item_type: ASCII}}
|
||||||
|
- {name: pralert, shape: {kind: scalar, item_type: BINARY_BYTE}}
|
||||||
|
|
||||||
|
- id: S16F8
|
||||||
|
stream: 16
|
||||||
|
function: 8
|
||||||
|
builder: s16f8_pr_job_monitor_ack
|
||||||
|
body: {kind: scalar, item_type: BINARY_BYTE, enum: HostCmdAck, param: ack}
|
||||||
|
|
||||||
|
# S16F15 / S16F16 — PRJobCreateMultiple. Bulk create variant of
|
||||||
|
# S16F11; the host posts a batch of jobs in one round-trip. The
|
||||||
|
# equipment processes them in order and replies with a per-job ACK
|
||||||
|
# list so the host can tell which subset failed.
|
||||||
|
- id: S16F15
|
||||||
|
stream: 16
|
||||||
|
function: 15
|
||||||
|
w: true
|
||||||
|
builder: s16f15_pr_job_create_multi
|
||||||
|
parser: parse_s16f15
|
||||||
|
body:
|
||||||
|
kind: list
|
||||||
|
struct_name: PRJobCreateMultiRequest
|
||||||
|
fields:
|
||||||
|
- name: jobs
|
||||||
|
shape:
|
||||||
|
kind: list_of
|
||||||
|
element:
|
||||||
|
kind: list
|
||||||
|
struct_name: PRJobCreateEntry
|
||||||
|
fields:
|
||||||
|
- {name: prjobid, shape: {kind: scalar, item_type: ASCII}}
|
||||||
|
- {name: ppid, shape: {kind: scalar, item_type: ASCII}}
|
||||||
|
- name: mtrloutspec
|
||||||
|
shape: {kind: list_of, element: {kind: scalar, item_type: ASCII}}
|
||||||
|
|
||||||
|
- id: S16F16
|
||||||
|
stream: 16
|
||||||
|
function: 16
|
||||||
|
builder: s16f16_pr_job_create_multi_ack
|
||||||
|
parser: parse_s16f16
|
||||||
|
body:
|
||||||
|
kind: list
|
||||||
|
struct_name: PRJobCreateMultiAck
|
||||||
|
fields:
|
||||||
|
- name: results
|
||||||
|
shape:
|
||||||
|
kind: list_of
|
||||||
|
element:
|
||||||
|
kind: list
|
||||||
|
struct_name: PRJobCreateMultiResult
|
||||||
|
fields:
|
||||||
|
- {name: prjobid, shape: {kind: scalar, item_type: ASCII}}
|
||||||
|
- {name: ack, shape: {kind: scalar, item_type: BINARY_BYTE, enum: HostCmdAck}}
|
||||||
|
|
||||||
# S16F27 / F28 — CJobCommand (E94). Host issues Start / Pause /
|
# S16F27 / F28 — CJobCommand (E94). Host issues Start / Pause /
|
||||||
# Resume / Stop / Abort against an existing CJ.
|
# Resume / Stop / Abort against an existing CJ.
|
||||||
- id: S16F27
|
- id: S16F27
|
||||||
|
|||||||
@@ -649,6 +649,48 @@ TEST_CASE("S16F9 PRJobAlert round-trip with typed state byte") {
|
|||||||
CHECK(parsed->prjobstate == ProcessJobState::Processing);
|
CHECK(parsed->prjobstate == ProcessJobState::Processing);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("S16F7 / S16F8 PRJobMonitor round-trip") {
|
||||||
|
std::vector<PRJobMonitorEntry> entries{
|
||||||
|
{"PJ-1", 0x80}, // enable
|
||||||
|
{"PJ-2", 0x00}, // disable
|
||||||
|
};
|
||||||
|
auto m = s16f7_pr_job_monitor(entries);
|
||||||
|
CHECK(m.stream == 16);
|
||||||
|
CHECK(m.function == 7);
|
||||||
|
CHECK(m.reply_expected);
|
||||||
|
|
||||||
|
auto parsed = parse_s16f7(m);
|
||||||
|
REQUIRE(parsed.has_value());
|
||||||
|
REQUIRE(parsed->entries.size() == 2);
|
||||||
|
CHECK(parsed->entries[0].prjobid == "PJ-1");
|
||||||
|
CHECK(parsed->entries[0].pralert == 0x80);
|
||||||
|
|
||||||
|
CHECK(*ack_byte(s16f8_pr_job_monitor_ack(HostCmdAck::Accept)) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("S16F15 / S16F16 PRJobCreateMultiple round-trip") {
|
||||||
|
std::vector<PRJobCreateEntry> jobs{
|
||||||
|
{"PJ-A", "RECIPE-1", {"W1"}},
|
||||||
|
{"PJ-B", "RECIPE-2", {"W2", "W3"}},
|
||||||
|
};
|
||||||
|
auto m = s16f15_pr_job_create_multi(jobs);
|
||||||
|
auto parsed = parse_s16f15(m);
|
||||||
|
REQUIRE(parsed.has_value());
|
||||||
|
REQUIRE(parsed->jobs.size() == 2);
|
||||||
|
CHECK(parsed->jobs[0].prjobid == "PJ-A");
|
||||||
|
CHECK(parsed->jobs[1].mtrloutspec.size() == 2);
|
||||||
|
|
||||||
|
std::vector<PRJobCreateMultiResult> results{
|
||||||
|
{"PJ-A", HostCmdAck::Accept},
|
||||||
|
{"PJ-B", HostCmdAck::Rejected},
|
||||||
|
};
|
||||||
|
auto ackmsg = s16f16_pr_job_create_multi_ack(results);
|
||||||
|
auto pack = parse_s16f16(ackmsg);
|
||||||
|
REQUIRE(pack.has_value());
|
||||||
|
REQUIRE(pack->results.size() == 2);
|
||||||
|
CHECK(pack->results[1].ack == HostCmdAck::Rejected);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE("S16F27 / S16F28 CJobCommand round-trip") {
|
TEST_CASE("S16F27 / S16F28 CJobCommand round-trip") {
|
||||||
auto req = s16f27_cj_command("CJ-1", "CJSTART");
|
auto req = s16f27_cj_command("CJ-1", "CJSTART");
|
||||||
auto parsed = parse_s16f27(req);
|
auto parsed = parse_s16f27(req);
|
||||||
|
|||||||
Reference in New Issue
Block a user