#pragma once #include #include #include #include #include #include #include "secsgem/gem/control_state.hpp" #include "secsgem/gem/data_model.hpp" #include "secsgem/secs2/item.hpp" #include "secsgem/secs2/message.hpp" // GEM (E30 / E5) message builders for the SxFy primaries and replies the demo // and tests exercise. Builders construct a `secs2::Message`; parsers do the // inverse and return `std::optional<...>` on shape mismatch. namespace secsgem::gem { namespace s2 = secsgem::secs2; // ------------------------------------------------------------------------- // small Item helpers // ------------------------------------------------------------------------- inline std::optional as_u4_scalar(const s2::Item& item) { if (item.format() != s2::Format::U4) return std::nullopt; const auto& v = std::get>(item.storage()); if (v.empty()) return std::nullopt; return v.front(); } inline std::optional as_binary_first(const s2::Item& item) { if (item.format() != s2::Format::Binary) return std::nullopt; const auto& v = item.as_bytes(); if (v.empty()) return std::nullopt; return v.front(); } inline std::optional as_ascii(const s2::Item& item) { if (item.format() != s2::Format::ASCII) return std::nullopt; return item.as_ascii(); } inline s2::Item u4_list(const std::vector& ids) { return s2::Item::u4(ids); // a single U4 array — SECS-II elements, not a list of items } // ------------------------------------------------------------------------- // S1F1 / S1F2 Are You There / On Line Data // ------------------------------------------------------------------------- inline s2::Message s1f1_are_you_there() { return s2::Message(1, 1, true); } inline s2::Message s1f2_on_line_data(const std::string& mdln, const std::string& softrev) { return s2::Message(1, 2, false, s2::Item::list({s2::Item::ascii(mdln), s2::Item::ascii(softrev)})); } // ------------------------------------------------------------------------- // S1F3 / S1F4 Selected Equipment Status Request / Data // // S1F3 W: ... > (n=0 means "all SVIDs") // S1F4 : ... > (empty list for unknown SVIDs per E5) // ------------------------------------------------------------------------- inline s2::Message s1f3_selected_status_request(const std::vector& svids) { s2::Item::List children; children.reserve(svids.size()); for (auto id : svids) children.push_back(s2::Item::u4(id)); return s2::Message(1, 3, true, s2::Item::list(std::move(children))); } inline std::optional> parse_s1f3(const s2::Message& m) { if (!m.body || !m.body->is_list()) return std::nullopt; std::vector out; for (const auto& child : m.body->as_list()) { auto v = as_u4_scalar(child); if (!v) return std::nullopt; out.push_back(*v); } return out; } inline s2::Message s1f4_selected_status_data(const std::vector>& values) { s2::Item::List children; children.reserve(values.size()); for (const auto& v : values) { children.push_back(v ? *v : s2::Item::list({})); // empty L for unknown } return s2::Message(1, 4, false, s2::Item::list(std::move(children))); } // ------------------------------------------------------------------------- // S1F11 / S1F12 Status Variable Namelist Request / Data // // S1F11 W: ... > (n=0 = all) // S1F12 : > ... > // ------------------------------------------------------------------------- inline s2::Message s1f11_status_namelist_request(const std::vector& svids) { s2::Item::List children; children.reserve(svids.size()); for (auto id : svids) children.push_back(s2::Item::u4(id)); return s2::Message(1, 11, true, s2::Item::list(std::move(children))); } inline s2::Message s1f12_status_namelist_data(const std::vector& items) { s2::Item::List children; children.reserve(items.size()); for (const auto& sv : items) { children.push_back(s2::Item::list( {s2::Item::u4(sv.id), s2::Item::ascii(sv.name), s2::Item::ascii(sv.units)})); } return s2::Message(1, 12, false, s2::Item::list(std::move(children))); } struct StatusName { uint32_t id; std::string name; std::string units; }; inline std::optional> parse_s1f12(const s2::Message& m) { if (!m.body || !m.body->is_list()) return std::nullopt; std::vector out; for (const auto& row : m.body->as_list()) { if (!row.is_list() || row.as_list().size() != 3) return std::nullopt; auto id = as_u4_scalar(row.as_list()[0]); auto name = as_ascii(row.as_list()[1]); auto units = as_ascii(row.as_list()[2]); if (!id || !name || !units) return std::nullopt; out.push_back({*id, *name, *units}); } return out; } // ------------------------------------------------------------------------- // S1F13 / S1F14 Establish Communications // ------------------------------------------------------------------------- inline s2::Message s1f13_establish_comms(const std::string& mdln, const std::string& softrev) { return s2::Message(1, 13, true, s2::Item::list({s2::Item::ascii(mdln), s2::Item::ascii(softrev)})); } inline s2::Message s1f14_establish_comms_ack(CommAck ack, const std::string& mdln, const std::string& softrev) { return s2::Message( 1, 14, false, s2::Item::list({s2::Item::binary({static_cast(ack)}), s2::Item::list({s2::Item::ascii(mdln), s2::Item::ascii(softrev)})})); } // ------------------------------------------------------------------------- // S1F15 / S1F16 Request OFFLINE / OFFLINE Acknowledge // S1F17 / S1F18 Request ONLINE / ONLINE Acknowledge // ------------------------------------------------------------------------- inline s2::Message s1f15_request_offline() { return s2::Message(1, 15, true); } inline s2::Message s1f16_offline_ack(OfflineAck ack) { return s2::Message(1, 16, false, s2::Item::binary({static_cast(ack)})); } inline s2::Message s1f17_request_online() { return s2::Message(1, 17, true); } inline s2::Message s1f18_online_ack(OnlineAck ack) { return s2::Message(1, 18, false, s2::Item::binary({static_cast(ack)})); } // Generic helper for messages whose body is a single . inline std::optional ack_byte(const s2::Message& msg) { if (!msg.body) return std::nullopt; return as_binary_first(*msg.body); } // ------------------------------------------------------------------------- // S2F13 / S2F14 Equipment Constant Request / Data // S2F15 / S2F16 New Equipment Constant Send / Acknowledge // ------------------------------------------------------------------------- inline s2::Message s2f13_ec_request(const std::vector& ecids) { s2::Item::List children; children.reserve(ecids.size()); for (auto id : ecids) children.push_back(s2::Item::u4(id)); return s2::Message(2, 13, true, s2::Item::list(std::move(children))); } inline s2::Message s2f14_ec_data(const std::vector& values) { return s2::Message(2, 14, false, s2::Item::list(values)); } inline s2::Message s2f15_ec_send( const std::vector>& sets) { s2::Item::List rows; rows.reserve(sets.size()); for (const auto& [id, val] : sets) { rows.push_back(s2::Item::list({s2::Item::u4(id), val})); } return s2::Message(2, 15, true, s2::Item::list(std::move(rows))); } inline std::optional>> parse_s2f15( const s2::Message& m) { if (!m.body || !m.body->is_list()) return std::nullopt; std::vector> out; for (const auto& row : m.body->as_list()) { if (!row.is_list() || row.as_list().size() != 2) return std::nullopt; auto id = as_u4_scalar(row.as_list()[0]); if (!id) return std::nullopt; out.emplace_back(*id, row.as_list()[1]); } return out; } inline s2::Message s2f16_ec_ack(EquipmentAck eac) { return s2::Message(2, 16, false, s2::Item::binary({static_cast(eac)})); } // ------------------------------------------------------------------------- // S2F17 / S2F18 Date and Time Request / Data // S2F31 / S2F32 Date and Time Set Request / Acknowledge // ------------------------------------------------------------------------- inline s2::Message s2f17_date_time_request() { return s2::Message(2, 17, true); } inline s2::Message s2f18_date_time_data(const std::string& time_str) { return s2::Message(2, 18, false, s2::Item::ascii(time_str)); } inline std::optional parse_s2f18(const s2::Message& m) { if (!m.body) return std::nullopt; return as_ascii(*m.body); } inline s2::Message s2f31_date_time_set(const std::string& time_str) { return s2::Message(2, 31, true, s2::Item::ascii(time_str)); } inline std::optional parse_s2f31(const s2::Message& m) { if (!m.body) return std::nullopt; return as_ascii(*m.body); } inline s2::Message s2f32_date_time_ack(TimeAck ack) { return s2::Message(2, 32, false, s2::Item::binary({static_cast(ack)})); } // ------------------------------------------------------------------------- // S2F41 / S2F42 Host Command / Host Command Acknowledge // // S2F41 W: >>> // S2F42 : >>> // ------------------------------------------------------------------------- inline s2::Message s2f41_host_command( const std::string& rcmd, const std::vector& params = {}) { s2::Item::List param_rows; param_rows.reserve(params.size()); for (const auto& p : params) { param_rows.push_back(s2::Item::list({s2::Item::ascii(p.name), p.value})); } return s2::Message( 2, 41, true, s2::Item::list({s2::Item::ascii(rcmd), s2::Item::list(std::move(param_rows))})); } struct HostCommand { std::string rcmd; std::vector params; }; inline std::optional parse_s2f41(const s2::Message& m) { if (!m.body || !m.body->is_list() || m.body->as_list().size() != 2) return std::nullopt; const auto& outer = m.body->as_list(); auto rcmd = as_ascii(outer[0]); if (!rcmd) return std::nullopt; HostCommand cmd{*rcmd, {}}; if (!outer[1].is_list()) return std::nullopt; for (const auto& row : outer[1].as_list()) { if (!row.is_list() || row.as_list().size() != 2) return std::nullopt; auto name = as_ascii(row.as_list()[0]); if (!name) return std::nullopt; cmd.params.push_back({*name, row.as_list()[1]}); } return cmd; } inline s2::Message s2f42_host_command_ack( HostCmdAck hcack, const std::vector>& cpacks = {}) { s2::Item::List cp_rows; cp_rows.reserve(cpacks.size()); for (const auto& [name, code] : cpacks) { cp_rows.push_back(s2::Item::list({s2::Item::ascii(name), s2::Item::binary({code})})); } return s2::Message( 2, 42, false, s2::Item::list({s2::Item::binary({static_cast(hcack)}), s2::Item::list(std::move(cp_rows))})); } struct HostCommandReply { HostCmdAck hcack; std::vector> cpacks; }; inline std::optional parse_s2f42(const s2::Message& m) { if (!m.body || !m.body->is_list() || m.body->as_list().size() != 2) return std::nullopt; const auto& outer = m.body->as_list(); auto hcack = as_binary_first(outer[0]); if (!hcack) return std::nullopt; HostCommandReply rep{static_cast(*hcack), {}}; if (!outer[1].is_list()) return std::nullopt; for (const auto& row : outer[1].as_list()) { if (!row.is_list() || row.as_list().size() != 2) return std::nullopt; auto name = as_ascii(row.as_list()[0]); auto code = as_binary_first(row.as_list()[1]); if (!name || !code) return std::nullopt; rep.cpacks.emplace_back(*name, *code); } return rep; } // ------------------------------------------------------------------------- // S10F1 / S10F2 Terminal Display, Single (host -> equipment) // S10F3 / S10F4 Terminal Display, Single (equipment -> host) // ------------------------------------------------------------------------- inline s2::Message s10f1_terminal_display_single(uint8_t tid, const std::string& text) { return s2::Message(10, 1, true, s2::Item::list({s2::Item::binary({tid}), s2::Item::ascii(text)})); } struct TerminalDisplay { uint8_t tid; std::string text; }; inline std::optional parse_terminal_display(const s2::Message& m) { if (!m.body || !m.body->is_list() || m.body->as_list().size() != 2) return std::nullopt; const auto& l = m.body->as_list(); auto tid = as_binary_first(l[0]); auto text = as_ascii(l[1]); if (!tid || !text) return std::nullopt; return TerminalDisplay{*tid, *text}; } inline s2::Message s10f2_terminal_display_ack(TerminalAck ack) { return s2::Message(10, 2, false, s2::Item::binary({static_cast(ack)})); } inline s2::Message s10f3_terminal_display_single(uint8_t tid, const std::string& text) { return s2::Message(10, 3, true, s2::Item::list({s2::Item::binary({tid}), s2::Item::ascii(text)})); } inline s2::Message s10f4_terminal_display_ack(TerminalAck ack) { return s2::Message(10, 4, false, s2::Item::binary({static_cast(ack)})); } // ========================================================================= // Extended GEM SxFy (events, alarms, recipes) // ========================================================================= // Generic > body reader, used by several messages. inline std::optional> parse_u4_list_body(const s2::Message& m) { if (!m.body || !m.body->is_list()) return std::nullopt; std::vector out; for (const auto& c : m.body->as_list()) { auto v = as_u4_scalar(c); if (!v) return std::nullopt; out.push_back(*v); } return out; } inline s2::Item u4_list_item(const std::vector& ids) { s2::Item::List children; children.reserve(ids.size()); for (auto id : ids) children.push_back(s2::Item::u4(id)); return s2::Item::list(std::move(children)); } // ------------------------------------------------------------------------- // S2F29 / S2F30 Equipment Constant Namelist Request / Data // S2F30: >> // ------------------------------------------------------------------------- inline s2::Message s2f29_ec_namelist_request(const std::vector& ecids) { return s2::Message(2, 29, true, u4_list_item(ecids)); } inline s2::Message s2f30_ec_namelist_data(const std::vector& ecs) { s2::Item::List rows; rows.reserve(ecs.size()); for (const auto& ec : ecs) { rows.push_back(s2::Item::list({ s2::Item::u4(ec.id), s2::Item::ascii(ec.name), s2::Item::ascii(ec.min_str), s2::Item::ascii(ec.max_str), s2::Item::ascii(""), // ECDEF rendering left simple s2::Item::ascii(ec.units), })); } return s2::Message(2, 30, false, s2::Item::list(std::move(rows))); } // ------------------------------------------------------------------------- // S2F33 / S2F34 Define Report // // S2F33 W: >>>> // S2F34 : // ------------------------------------------------------------------------- inline s2::Message s2f33_define_report( uint32_t dataid, const std::vector>>& reports) { s2::Item::List rows; rows.reserve(reports.size()); for (const auto& [rptid, vids] : reports) { rows.push_back(s2::Item::list({s2::Item::u4(rptid), u4_list_item(vids)})); } return s2::Message(2, 33, true, s2::Item::list({s2::Item::u4(dataid), s2::Item::list(std::move(rows))})); } struct DefineReportRequest { uint32_t dataid; std::vector>> reports; }; inline std::optional parse_s2f33(const s2::Message& m) { if (!m.body || !m.body->is_list() || m.body->as_list().size() != 2) return std::nullopt; const auto& outer = m.body->as_list(); auto dataid = as_u4_scalar(outer[0]); if (!dataid) return std::nullopt; DefineReportRequest req{*dataid, {}}; if (!outer[1].is_list()) return std::nullopt; for (const auto& row : outer[1].as_list()) { if (!row.is_list() || row.as_list().size() != 2) return std::nullopt; auto rptid = as_u4_scalar(row.as_list()[0]); if (!rptid) return std::nullopt; auto vids = std::vector{}; if (!row.as_list()[1].is_list()) return std::nullopt; for (const auto& v : row.as_list()[1].as_list()) { auto u = as_u4_scalar(v); if (!u) return std::nullopt; vids.push_back(*u); } req.reports.emplace_back(*rptid, std::move(vids)); } return req; } inline s2::Message s2f34_define_report_ack(DefineReportAck ack) { return s2::Message(2, 34, false, s2::Item::binary({static_cast(ack)})); } // ------------------------------------------------------------------------- // S2F35 / S2F36 Link Event Report // // S2F35 W: >>>> // S2F36 : // ------------------------------------------------------------------------- inline s2::Message s2f35_link_event_report( uint32_t dataid, const std::vector>>& links) { s2::Item::List rows; rows.reserve(links.size()); for (const auto& [ceid, rpts] : links) { rows.push_back(s2::Item::list({s2::Item::u4(ceid), u4_list_item(rpts)})); } return s2::Message(2, 35, true, s2::Item::list({s2::Item::u4(dataid), s2::Item::list(std::move(rows))})); } struct LinkEventReportRequest { uint32_t dataid; std::vector>> links; }; inline std::optional parse_s2f35(const s2::Message& m) { if (!m.body || !m.body->is_list() || m.body->as_list().size() != 2) return std::nullopt; const auto& outer = m.body->as_list(); auto dataid = as_u4_scalar(outer[0]); if (!dataid) return std::nullopt; LinkEventReportRequest req{*dataid, {}}; if (!outer[1].is_list()) return std::nullopt; for (const auto& row : outer[1].as_list()) { if (!row.is_list() || row.as_list().size() != 2) return std::nullopt; auto ceid = as_u4_scalar(row.as_list()[0]); if (!ceid) return std::nullopt; std::vector rpts; if (!row.as_list()[1].is_list()) return std::nullopt; for (const auto& v : row.as_list()[1].as_list()) { auto u = as_u4_scalar(v); if (!u) return std::nullopt; rpts.push_back(*u); } req.links.emplace_back(*ceid, std::move(rpts)); } return req; } inline s2::Message s2f36_link_event_report_ack(LinkEventAck ack) { return s2::Message(2, 36, false, s2::Item::binary({static_cast(ack)})); } // ------------------------------------------------------------------------- // S2F37 / S2F38 Enable / Disable Event Report // // S2F37 W: >> // S2F38 : // ------------------------------------------------------------------------- inline s2::Message s2f37_enable_event(bool enable, const std::vector& ceids) { return s2::Message(2, 37, true, s2::Item::list({s2::Item::boolean(enable), u4_list_item(ceids)})); } struct EnableEventRequest { bool enable; std::vector ceids; }; inline std::optional parse_s2f37(const s2::Message& m) { if (!m.body || !m.body->is_list() || m.body->as_list().size() != 2) return std::nullopt; const auto& outer = m.body->as_list(); if (outer[0].format() != s2::Format::Boolean) return std::nullopt; const auto& bytes = outer[0].as_bytes(); if (bytes.empty()) return std::nullopt; EnableEventRequest req{bytes.front() != 0, {}}; if (!outer[1].is_list()) return std::nullopt; for (const auto& v : outer[1].as_list()) { auto u = as_u4_scalar(v); if (!u) return std::nullopt; req.ceids.push_back(*u); } return req; } inline s2::Message s2f38_enable_event_ack(EnableEventAck ack) { return s2::Message(2, 38, false, s2::Item::binary({static_cast(ack)})); } // ------------------------------------------------------------------------- // S5F1 / S5F2 Alarm Report Send / Ack // // S5F1 W: > // S5F2 : // ------------------------------------------------------------------------- inline s2::Message s5f1_alarm_report(uint8_t alcd, uint32_t alid, const std::string& altx) { return s2::Message(5, 1, true, s2::Item::list({s2::Item::binary({alcd}), s2::Item::u4(alid), s2::Item::ascii(altx)})); } struct AlarmReport { uint8_t alcd; uint32_t alid; std::string altx; bool is_set() const { return (alcd & 0x80) != 0; } uint8_t category() const { return alcd & 0x7F; } }; inline std::optional parse_s5f1(const s2::Message& m) { if (!m.body || !m.body->is_list() || m.body->as_list().size() != 3) return std::nullopt; const auto& l = m.body->as_list(); auto alcd = as_binary_first(l[0]); auto alid = as_u4_scalar(l[1]); auto altx = as_ascii(l[2]); if (!alcd || !alid || !altx) return std::nullopt; return AlarmReport{*alcd, *alid, *altx}; } inline s2::Message s5f2_alarm_ack(AlarmAck ack) { return s2::Message(5, 2, false, s2::Item::binary({static_cast(ack)})); } // ------------------------------------------------------------------------- // S5F3 / S5F4 Enable / Disable Alarm Send // // S5F3 W: > (ALED 0x80 = enable, 0x00 = disable) // S5F4 : // ------------------------------------------------------------------------- inline constexpr uint8_t kAlarmEnableByte = 0x80; inline constexpr uint8_t kAlarmDisableByte = 0x00; inline s2::Message s5f3_enable_alarm(bool enable, uint32_t alid) { return s2::Message( 5, 3, true, s2::Item::list({s2::Item::binary({enable ? kAlarmEnableByte : kAlarmDisableByte}), s2::Item::u4(alid)})); } struct EnableAlarmRequest { bool enable; uint32_t alid; }; inline std::optional parse_s5f3(const s2::Message& m) { if (!m.body || !m.body->is_list() || m.body->as_list().size() != 2) return std::nullopt; const auto& l = m.body->as_list(); auto aled = as_binary_first(l[0]); auto alid = as_u4_scalar(l[1]); if (!aled || !alid) return std::nullopt; return EnableAlarmRequest{(*aled & 0x80) != 0, *alid}; } inline s2::Message s5f4_enable_alarm_ack(AlarmAck ack) { return s2::Message(5, 4, false, s2::Item::binary({static_cast(ack)})); } // ------------------------------------------------------------------------- // S5F5 / S5F6 List Alarms Request / Data // // S5F5 W: > (empty = all) // S5F6 : >> // ------------------------------------------------------------------------- inline s2::Message s5f5_list_alarms_request(const std::vector& alids) { return s2::Message(5, 5, true, u4_list_item(alids)); } inline s2::Message s5f6_list_alarms_data(const std::vector& alarms, const std::function& active) { s2::Item::List rows; rows.reserve(alarms.size()); for (const auto& a : alarms) { const uint8_t alcd = (a.severity_category & 0x7F) | static_cast(active(a.id) ? 0x80 : 0x00); rows.push_back(s2::Item::list( {s2::Item::binary({alcd}), s2::Item::u4(a.id), s2::Item::ascii(a.text)})); } return s2::Message(5, 6, false, s2::Item::list(std::move(rows))); } // ------------------------------------------------------------------------- // S6F11 / S6F12 Event Report Send // // S6F11 W: >>>> // S6F12 : // ------------------------------------------------------------------------- inline s2::Message s6f11_event_report(uint32_t dataid, uint32_t ceid, const std::vector& reports) { s2::Item::List rows; rows.reserve(reports.size()); for (const auto& r : reports) { s2::Item::List values(r.values.begin(), r.values.end()); rows.push_back(s2::Item::list({s2::Item::u4(r.rptid), s2::Item::list(std::move(values))})); } return s2::Message(6, 11, true, s2::Item::list({s2::Item::u4(dataid), s2::Item::u4(ceid), s2::Item::list(std::move(rows))})); } struct EventReportMessage { uint32_t dataid; uint32_t ceid; std::vector reports; }; inline std::optional parse_s6f11(const s2::Message& m) { if (!m.body || !m.body->is_list() || m.body->as_list().size() != 3) return std::nullopt; const auto& outer = m.body->as_list(); auto dataid = as_u4_scalar(outer[0]); auto ceid = as_u4_scalar(outer[1]); if (!dataid || !ceid) return std::nullopt; EventReportMessage out{*dataid, *ceid, {}}; if (!outer[2].is_list()) return std::nullopt; for (const auto& row : outer[2].as_list()) { if (!row.is_list() || row.as_list().size() != 2) return std::nullopt; auto rptid = as_u4_scalar(row.as_list()[0]); if (!rptid) return std::nullopt; if (!row.as_list()[1].is_list()) return std::nullopt; ReportData rd{*rptid, {}}; for (const auto& v : row.as_list()[1].as_list()) rd.values.push_back(v); out.reports.push_back(std::move(rd)); } return out; } inline s2::Message s6f12_event_report_ack(EventReportAck ack) { return s2::Message(6, 12, false, s2::Item::binary({static_cast(ack)})); } // ------------------------------------------------------------------------- // S7F3 / S7F4 Process Program Send / Ack // S7F5 / S7F6 Process Program Request / Data // S7F19/ S7F20 Current EPPD List Request / Data // ------------------------------------------------------------------------- inline s2::Message s7f3_process_program_send(const std::string& ppid, const std::string& ppbody) { return s2::Message( 7, 3, true, s2::Item::list({s2::Item::ascii(ppid), s2::Item::binary(std::vector(ppbody.begin(), ppbody.end()))})); } struct ProcessProgram { std::string ppid; std::string ppbody; }; inline std::optional parse_s7f3(const s2::Message& m) { if (!m.body || !m.body->is_list() || m.body->as_list().size() != 2) return std::nullopt; const auto& l = m.body->as_list(); auto ppid = as_ascii(l[0]); if (!ppid) return std::nullopt; if (l[1].format() != s2::Format::Binary) return std::nullopt; const auto& bytes = l[1].as_bytes(); return ProcessProgram{*ppid, std::string(bytes.begin(), bytes.end())}; } inline s2::Message s7f4_process_program_ack(ProcessProgramAck ack) { return s2::Message(7, 4, false, s2::Item::binary({static_cast(ack)})); } inline s2::Message s7f5_process_program_request(const std::string& ppid) { return s2::Message(7, 5, true, s2::Item::ascii(ppid)); } inline std::optional parse_s7f5(const s2::Message& m) { if (!m.body) return std::nullopt; return as_ascii(*m.body); } inline s2::Message s7f6_process_program_data(const std::string& ppid, const std::string& ppbody) { return s2::Message( 7, 6, false, s2::Item::list({s2::Item::ascii(ppid), s2::Item::binary(std::vector(ppbody.begin(), ppbody.end()))})); } inline std::optional parse_s7f6(const s2::Message& m) { return parse_s7f3(m); // identical body shape } inline s2::Message s7f19_current_eppd_request() { return s2::Message(7, 19, true); } inline s2::Message s7f20_current_eppd_data(const std::vector& ppids) { s2::Item::List rows; rows.reserve(ppids.size()); for (const auto& p : ppids) rows.push_back(s2::Item::ascii(p)); return s2::Message(7, 20, false, s2::Item::list(std::move(rows))); } inline std::optional> parse_s7f20(const s2::Message& m) { if (!m.body || !m.body->is_list()) return std::nullopt; std::vector out; for (const auto& c : m.body->as_list()) { auto s = as_ascii(c); if (!s) return std::nullopt; out.push_back(*s); } return out; } } // namespace secsgem::gem