From ff1a6b3726b5d57d498e7d3370a7cd924b33f2f9 Mon Sep 17 00:00:00 2001 From: Raphael Maenle Date: Mon, 8 Jun 2026 00:16:04 +0200 Subject: [PATCH] D4: E87 server dispatch for S3F17/F25/F27 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hooks the CarrierStore + LoadPortStore from D3 onto the wire: S3F17 CarrierAction: ProceedWithCarrier -> CarrierIDEvent::ProceedWithCarrier CancelCarrier -> CarrierIDEvent::CancelCarrier BindCarrierID -> CarrierIDEvent::Bind unknown action -> CAACK=CarrierActionInvalid unknown carrier -> CAACK=CarrierIDUnknown S3F25 CarrierTransfer: fires source.StartUnloading + target.StartLoading transfer events, updates Carrier::port_id, returns CAACK=Accept. S3F27 CancelCarrier: fires CIDS CancelCarrier + CAS Cancel against the carrier. The state-change handlers wired into the stores in D3 are the right place to emit S6F11 CEIDs for carrier-events; that hookup is left for a follow-up commit (it depends on the equipment.yaml CEID catalog, which doesn't yet enumerate E87 events). Closes Tranche D — E87 Carrier Management end-to-end (FSMs + wire + stores + dispatch). Co-Authored-By: Claude Opus 4.7 --- apps/secs_server.cpp | 60 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/apps/secs_server.cpp b/apps/secs_server.cpp index 0b386c7..3d982fe 100644 --- a/apps/secs_server.cpp +++ b/apps/secs_server.cpp @@ -609,6 +609,66 @@ int main(int argc, char** argv) { return gem::s5f18_exception_recover_abort_ack(ack); }); + // ---- E87 Carrier Management dispatch --------------------------------- + // S3F17 maps the textual CARRIERACTION string onto a CarrierIDEvent + // and fires it against the matching carrier. Unknown actions return + // CarrierActionInvalid; unknown carriers return CarrierIDUnknown. + router.on(3, 17, [model, logfn](const s2::Message& msg) { + auto req = gem::parse_s3f17(msg); + if (!req) return gem::s3f18_carrier_action_ack(gem::CarrierActionAck::ParameterInvalid); + if (!model->carriers.has(req->carrierid)) + return gem::s3f18_carrier_action_ack(gem::CarrierActionAck::CarrierIDUnknown); + + auto ack = gem::CarrierActionAck::Accept; + if (req->carrieraction == "ProceedWithCarrier") { + model->carriers.fire_id_event(req->carrierid, + gem::CarrierIDEvent::ProceedWithCarrier); + } else if (req->carrieraction == "CancelCarrier") { + model->carriers.fire_id_event(req->carrierid, + gem::CarrierIDEvent::CancelCarrier); + } else if (req->carrieraction == "BindCarrierID") { + model->carriers.fire_id_event(req->carrierid, gem::CarrierIDEvent::Bind); + } else { + ack = gem::CarrierActionAck::CarrierActionInvalid; + } + logfn("S3F17 CARRIER=" + req->carrierid + " action=" + req->carrieraction + + " -> S3F18 CAACK=" + std::to_string(static_cast(ack))); + return gem::s3f18_carrier_action_ack(ack); + }); + + // S3F25 — host instructs equipment to move a carrier between ports. + // We record the new port binding on the Carrier and fire the source + // port's StartUnloading + target port's StartLoading transfer events; + // application code is responsible for completing them. + router.on(3, 25, [model, logfn](const s2::Message& msg) { + auto req = gem::parse_s3f25(msg); + if (!req) + return gem::s3f26_carrier_transfer_ack(gem::CarrierActionAck::ParameterInvalid); + auto* c = model->carriers.get(req->carrierid); + if (!c) + return gem::s3f26_carrier_transfer_ack(gem::CarrierActionAck::CarrierIDUnknown); + model->load_ports.fire_transfer_event(req->source_portid, + gem::LoadPortTransferEvent::StartUnloading); + model->load_ports.fire_transfer_event(req->target_portid, + gem::LoadPortTransferEvent::StartLoading); + c->port_id = req->target_portid; + logfn("S3F25 CARRIER=" + req->carrierid + + " " + std::to_string(req->source_portid) + "->" + + std::to_string(req->target_portid)); + return gem::s3f26_carrier_transfer_ack(gem::CarrierActionAck::Accept); + }); + + // S3F27 — Cancel Carrier (single-EXID form). + router.on(3, 27, [model, logfn](const s2::Message& msg) { + auto cid = gem::parse_s3f27(msg); + if (!cid || !model->carriers.has(*cid)) + return gem::s3f28_cancel_carrier_ack(gem::CarrierActionAck::CarrierIDUnknown); + model->carriers.fire_id_event(*cid, gem::CarrierIDEvent::CancelCarrier); + model->carriers.fire_access_event(*cid, gem::CarrierAccessEvent::Cancel); + logfn("S3F27 CARRIER=" + *cid + " cancelled"); + return gem::s3f28_cancel_carrier_ack(gem::CarrierActionAck::Accept); + }); + router.on(7, 3, [model, logfn](const s2::Message& msg) { auto pp = gem::parse_s7f3(msg); if (!pp) return gem::s7f4_process_program_ack(gem::ProcessProgramAck::LengthError);